Repository: iamcco/coc-spell-checker Branch: master Commit: 51c484169de1 Files: 82 Total size: 198.0 KB Directory structure: gitextract_71n6cbrq/ ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── FAQ.md ├── License.txt ├── README.md ├── cspell.code-snippets ├── dicts/ │ ├── README.md │ └── vim/ │ ├── CHANGELOG.md │ ├── README.md │ ├── build-dict.js │ ├── cspell-ext.json │ ├── index.d.ts │ ├── index.js │ ├── link.js │ ├── package.json │ ├── unlink.js │ └── util.js ├── package.json ├── server/ │ ├── SuggestionsGenerator.d.ts │ ├── SuggestionsGenerator.js │ ├── SuggestionsGenerator.test.d.ts │ ├── SuggestionsGenerator.test.js │ ├── api.d.ts │ ├── api.js │ ├── autoLoad.d.ts │ ├── autoLoad.js │ ├── autoLoad.test.d.ts │ ├── autoLoad.test.js │ ├── codeActions.d.ts │ ├── codeActions.js │ ├── cspellConfig.d.ts │ ├── cspellConfig.js │ ├── documentSettings.d.ts │ ├── documentSettings.js │ ├── documentSettings.test.d.ts │ ├── documentSettings.test.js │ ├── index.d.ts │ ├── index.js │ ├── logger.d.ts │ ├── logger.js │ ├── logger.test.d.ts │ ├── logger.test.js │ ├── server.d.ts │ ├── server.js │ ├── util.d.ts │ ├── util.js │ ├── util.test.d.ts │ ├── util.test.js │ ├── validator.d.ts │ ├── validator.js │ ├── validator.test.d.ts │ ├── validator.test.js │ ├── vscode.config.d.ts │ └── vscode.config.js ├── src/ │ ├── client/ │ │ ├── client.ts │ │ └── index.ts │ ├── commands.ts │ ├── dict.ts │ ├── extensionApi.ts │ ├── fileTypes/ │ │ └── fileTypeMap.json │ ├── index.ts │ ├── iso639-1/ │ │ ├── index.ts │ │ └── languageCodes.ts │ ├── server/ │ │ ├── index.ts │ │ ├── server.ts │ │ └── serverSettings.ts │ ├── settings/ │ │ ├── CSpellSettings.ts │ │ ├── config.ts │ │ ├── index.ts │ │ ├── languageIds.ts │ │ └── settings.ts │ ├── statusbar.ts │ └── util/ │ ├── commonPrefix.ts │ ├── index.ts │ ├── pipe.ts │ ├── uriHelper.ts │ ├── util.ts │ └── watcher.ts ├── tsconfig.json ├── tslint.json └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ out/* dist/* coverage/* settingsViewer/* node_modules temp *.vsix *.cpuprofile server/*.js.map .DS_Store ================================================ FILE: .npmignore ================================================ server/*.d.ts server/*.js.map src/ yarn.lock webpack.config.js tslint.json tsconfig.json node_modules/ dicts/ ================================================ FILE: CHANGELOG.md ================================================ # Release Notes ## v1.0.0 - get it work ================================================ FILE: FAQ.md ================================================ # Spell Checker FAQ This is a place to capture common questions and possible confusions. Please feel free to make suggestion for things to be added to this file. See: [FAQ Issues](https://github.com/streetsidesoftware/vscode-spell-checker/issues?utf8=%E2%9C%93&q=label%3AFAQ+) ## Things to know ### *Is the spell checker case sensitive?* > The spell checker is NOT case sensitive. ### *What files are excluded by the spell checker?* > By default the spell checker excludes the same files excluded by the VS Code *search.exclude* setting. See discussion: [#16](https://github.com/streetsidesoftware/vscode-spell-checker/issues/16), [#55](https://github.com/streetsidesoftware/vscode-spell-checker/issues/55) and [#95](https://github.com/streetsidesoftware/vscode-spell-checker/issues/95) ### *Does the spell checker use any online services?* > No, the spell checker is self contained. It does not send your code off to a service to be checked. ### *Can I use the spell checker with other languages like Spanish or French?* > Yes, please visit [cspell-dicts](https://github.com/Jason3S/cspell-dicts). > See also: [#119](https://github.com/streetsidesoftware/vscode-spell-checker/issues/119) ### *Is it possible to only spell check comments* > Yes. It is necessary to define `includeRegExpList` for each language. See [#107](https://github.com/streetsidesoftware/vscode-spell-checker/issues/107) and [#116](https://github.com/streetsidesoftware/vscode-spell-checker/issues/116) ### Can I use the spell checker as a linter or part of the build process? > Yes you can: [Spell Checker as Npm package #137](https://github.com/streetsidesoftware/vscode-spell-checker/issues/137) ### Can I remove a word from the dictionary? > [How to remove word from dictionary? #117](https://github.com/streetsidesoftware/vscode-spell-checker/issues/117) ### Can I add a many words to the dictionary at once? > Yes, select the words and right click to get the menu. Choose `Add Word to Dictionary` or `Add Word to Global Dictionary`. [Add all words in the current document to dictionary #59](https://github.com/streetsidesoftware/vscode-spell-checker/issues/59) ### Can I ignore words. > See: [Option to ignore words #146](https://github.com/streetsidesoftware/vscode-spell-checker/issues/146) ================================================ FILE: License.txt ================================================ Copyright (c) Jason Dent All rights reserved. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Spelling Checker for (Neo)vim > fork from [vscode-spell-checker](https://github.com/streetsidesoftware/vscode-spell-checker) v1.7.24 and [commit](https://github.com/streetsidesoftware/vscode-spell-checker/commit/f9c2a9eabf3bc41f19d2d0216472afd003fa65bb) A basic spell checker that works well with camelCase code. The goal of this spell checker is to help catch common spelling errors while keeping the number of false positives low. ## Functionality Load a TypeScript, JavaScript, Text, etc. file. Words not in the dictionary files will have a squiggly underline. ![screenshot](https://user-images.githubusercontent.com/5492542/71676618-9e86f380-2dbb-11ea-9838-2291e895c7ff.png) To see the list of suggestions: ![screenshot](https://user-images.githubusercontent.com/5492542/71676668-bc545880-2dbb-11ea-8bcc-4bd9140ef959.png) Paste below configurations to your `init.vim` or `.vimrc` - Remap for do codeAction of selected region ``` vim vmap a (coc-codeaction-selected) nmap a (coc-codeaction-selected) ``` Then positioning the cursor in the word, any of the following should display the list of suggestions: - `aap` for current paragraph - `aw` for current word Or use the [coc-actions](https://github.com/iamcco/coc-actions) ![image](https://user-images.githubusercontent.com/5492542/71774253-78717700-2fa6-11ea-9629-1e88d0b5114e.png) ## Install `:CocInstall coc-spell-checker` ### Commands and Configurations - [commands](https://github.com/iamcco/coc-spell-checker/blob/master/package.json#L30) - [configurations](https://github.com/iamcco/coc-spell-checker/blob/master/package.json#L125) ## Supported Languages - English (US) - English (GB) - turn on by changing `"cSpell.language": "en"` to `"cSpell.language": "en-GB"` ## Add-On Dictionaries - [catalan](https://github.com/iamcco/coc-cspell-dicts) - [czech](https://github.com/iamcco/coc-cspell-dicts) - [danish](https://github.com/iamcco/coc-cspell-dicts) - [dutch](https://github.com/iamcco/coc-cspell-dicts) - [french](https://github.com/iamcco/coc-cspell-dicts) - [french-reforme](https://github.com/iamcco/coc-cspell-dicts) - [german](https://github.com/iamcco/coc-cspell-dicts) - [greek](https://github.com/iamcco/coc-cspell-dicts) - [italian](https://github.com/iamcco/coc-cspell-dicts) - [persian](https://github.com/iamcco/coc-cspell-dicts) - [polish](https://github.com/iamcco/coc-cspell-dicts) - [portuguese](https://github.com/iamcco/coc-cspell-dicts) - [portuguese-brazilian](https://github.com/iamcco/coc-cspell-dicts) - [russian](https://github.com/iamcco/coc-cspell-dicts) - [spanish](https://github.com/iamcco/coc-cspell-dicts) - [swedish](https://github.com/iamcco/coc-cspell-dicts) - [turkish](https://github.com/iamcco/coc-cspell-dicts) - [ukrainian](https://github.com/iamcco/coc-cspell-dicts) - [medical-terms](https://github.com/iamcco/coc-cspell-dicts) ## Enabled File Types - vim - AsciiDoc - C, C++ - C# - css, less, scss - Elixir - Go - Html - Java - JavaScript - JSON / JSONC - LaTex - Markdown - PHP - PowerShell - Pug / Jade - Python - reStructuredText - Rust - Scala - Text - TypeScript - YAML ## How it works with camelCase The concept is simple, split camelCase words before checking them against a list of known English words. - camelCase -> camel case - HTMLInput -> html input -- Notice that the `I` is associated with `Input` and not `HTML` - snake_case_words -> snake case words - camel2snake -> camel snake -- (the 2 is ignored) ### Special case will ALL CAPS words There are a few special cases to help will common spelling practices for ALL CAPS words. Trailing `s`, `ing`, `ies`, `es`, `ed` are kept with the previous word. - CURLs -> curls -- trailing `s` - CURLedRequest -> curled request -- trailing `ed` ## Things to note - This spellchecker is case insensitive. It will not catch errors like english which should be English. - The spellchecker uses a local word dictionary. It does not send anything outside your machine. - The words in the dictionary can and do contain errors. - There are missing words. - Only words longer than 3 characters are checked. "jsj" is ok, while "jsja" is not. - All symbols and punctuation are ignored. ## In Document Settings It is possible to add spell check settings into your source code. This is to help with file specific issues that may not be applicable to the entire project. All settings are prefixed with `cSpell:` or `spell-checker:`. - `disable` -- turn off the spell checker for a section of code. - `enable` -- turn the spell checker back on after it has been turned off. - `ignore` -- specify a list of words to be ignored. - `words` -- specify a list of words to be considered correct and will appear in the suggestions list. - `ignoreRegExp` -- Any text matching the regular expression will NOT be checked for spelling. - `includeRegExp` -- Only text matching the collection of includeRegExp will be checked. - `enableCompoundWords` / `disableCompoundWords` -- Allow / disallow words like: "stringlength". ### Enable / Disable checking sections of code It is possible to disable / enable the spell checker by adding comments to your code. #### Disable Checking - `/* cSpell:disable */` - `/* spell-checker: disable */` - `/* spellchecker: disable */` - `/* cspell: disable-line */` - `/* cspell: disable-next-line */` #### Enable Checking - `/* cSpell:enable */` - `/* spell-checker: enable */` - `/* spellchecker: enable */` #### Example ``` javascript // cSpell:disable const wackyWord = ['zaallano', 'wooorrdd', 'zzooommmmmmmm']; /* cSpell:enable */ // Nest disable / enable is not Supported // spell-checker:disable // It is now disabled. var liep = 1; /* cspell:disable */ // It is still disabled // cSpell:enable // It is now enabled const str = "goededag"; // <- will be flagged as an error. // spell-checker:enable <- doesn't do anything // cSPELL:DISABLE <-- also works. // if there isn't an enable, spelling is disabled till the end of the file. const str = "goedemorgen"; // <- will NOT be flagged as an error. ``` ### Ignore Ignore allows you the specify a list of words you want to ignore within the document. ```javascript // cSpell:ignore zaallano, wooorrdd // cSpell:ignore zzooommmmmmmm const wackyWord = ['zaallano', 'wooorrdd', 'zzooommmmmmmm']; ``` **Note:** words defined with `ignore` will be ignored for the entire file. ### Words The words list allows you to add words that will be considered correct and will be used as suggestions. ```javascript // cSpell:words woorxs sweeetbeat const companyName = 'woorxs sweeetbeat'; ``` **Note:** words defined with `words` will be used for the entire file. ### Enable / Disable compound words In some programing language it is common to glue words together. ```c // cSpell:enableCompoundWords char * errormessage; // Is ok with cSpell:enableCompoundWords int errornumber; // Is also ok. ``` **Note:** Compound word checking cannot be turned on / off in the same file. The last setting in the file determines the value for the entire file. ### Excluding and Including Text to be checked. By default, the entire document is checked for spelling. `cSpell:disable`/`cSpell:enable` above allows you to block off sections of the document. `ignoreRegExp` and `includeRegExp` give you the ability to ignore or include patterns of text. By default the flags `gim` are added if no flags are given. The spell checker works in the following way: 1. Find all text matching `includeRegExp` 2. Remove any text matching `excludeRegExp` 3. Check the remaining text. #### Exclude Example ```javascript // cSpell:ignoreRegExp 0x[0-9a-f]+ -- will ignore c style hex numbers // cSpell:ignoreRegExp /0x[0-9A-F]+/g -- will ignore upper case c style hex numbers. // cSpell:ignoreRegExp g{5} h{5} -- will only match ggggg, but not hhhhh or 'ggggg hhhhh' // cSpell:ignoreRegExp g{5}|h{5} -- will match both ggggg and hhhhh // cSpell:ignoreRegExp /g{5} h{5}/ -- will match 'ggggg hhhhh' /* cSpell:ignoreRegExp /n{5}/ -- will NOT work as expected because of the ending comment -> */ /* cSpell:ignoreRegExp /q{5}/ -- will match qqqqq just fine but NOT QQQQQ */ // cSpell:ignoreRegExp /[^\s]{40,}/ -- will ignore long strings with no spaces. // cSpell:ignoreRegExp Email -- this will ignore email like patterns -- see Predefined RegExp expressions var encodedImage = 'HR+cPzr7XGAOJNurPL0G8I2kU0UhKcqFssoKvFTR7z0T3VJfK37vS025uKroHfJ9nA6WWbHZ/ASn...'; var email1 = 'emailaddress@myfancynewcompany.com'; var email2 = ''; ``` **Note:** ignoreRegExp and includeRegExp are applied to the entire file. They do not start and stop. #### Include Example In general you should not need to use `includeRegExp`. But if you are mixing languages then it could come in helpful. ```Python # cSpell:includeRegExp #.* # cSpell:includeRegExp ("""|''')[^\1]*\1 # only comments and block strings will be checked for spelling. def sum_it(self, seq): """This is checked for spelling""" variabele = 0 alinea = 'this is not checked' for num in seq: # The local state of 'value' will be retained between iterations variabele += num yield variabele ``` ## Predefined RegExp expressions ### Exclude patterns * `Urls`1 -- Matches urls * `HexDigits` -- Matches hex digits: `/^x?[0-1a-f]+$/i` * `HexValues` -- Matches common hex format like #aaa, 0xfeef, \\u0134 * `EscapeCharacters`1 -- matches special characters: '\\n', '\\t' etc. * `Base64`1 -- matches base64 blocks of text longer than 40 characters. * `Email` -- matches most email addresses. ### Include Patterns * `Everything`1 -- By default we match an entire document and remove the excludes. * `string` -- This matches common string formats like '...', "...", and \`...\` * `CStyleComment` -- These are C Style comments /* */ and // * `PhpHereDoc` -- This matches PHPHereDoc strings. 1. These patterns are part of the default include/exclude list for every file. ## Customization ### Adding words to the Workspace Dictionary You have the option to add you own words to the workspace dictionary. The easiest, is to put your cursor on the word you wish to add, hit `aw` You will get a list of suggestions and the option to add the word. You can also type in a word you want to add to the dictionary: `:CocCommand cSpell.addWordToDictionary` and type in the word you wish to add. ### cSpell.json Words added to the dictionary are placed in the `cSpell.json` file in the `.vim` folder found in the _workspace_. Note, the settings in cSpell.json will override the equivalent cSpell settings in settings.json. #### Example _cSpell.json_ file ```javascript // cSpell Settings { // Version of the setting file. Always 0.1 "version": "0.1", // language - current active spelling language "language": "en", // words - list of words to be always considered correct "words": [ "mkdirp", "tsmerge", "githubusercontent", "streetsidesoftware", "vsmarketplacebadge", "visualstudio" ], // flagWords - list of words to be always considered incorrect // This is useful for offensive words and common spelling errors. // For example "hte" should be "the" "flagWords": [ "hte" ] } ``` ### Configuration Settings ```javascript //-------- Code Spell Checker Configuration -------- // The Language local to use when spell checking. "en" and "en-GB" are currently supported. "cSpell.language": "en", // Controls the maximum number of spelling errors per document. "cSpell.maxNumberOfProblems": 100, // Controls the number of suggestions shown. "cSpell.numSuggestions": 8, // The minimum length of a word before checking it against a dictionary. "cSpell.minWordLength": 4, // Specify file types to spell check. "cSpell.enabledLanguageIds": [ "csharp", "go", "javascript", "javascriptreact", "markdown", "php", "plaintext", "typescript", "typescriptreact", "yml" ], // Enable / Disable the spell checker. "cSpell.enabled": true, // Display the spell checker status on the status bar. "cSpell.showStatus": true, // Words to add to dictionary for a workspace. "cSpell.words": [], // Enable / Disable compound words like 'errormessage' "cSpell.allowCompoundWords": false, // Words to be ignored and not suggested. "cSpell.ignoreWords": ["behaviour"], // User words to add to dictionary. Should only be in the user settings. "cSpell.userWords": [], // Specify paths/files to ignore. "cSpell.ignorePaths": [ "node_modules", // this will ignore anything the node_modules directory "**/node_modules", // the same for this one "**/node_modules/**", // the same for this one "node_modules/**", // Doesn't currently work due to how the current working directory is determined. "vscode-extension", // ".git", // Ignore the .git directory "*.dll", // Ignore all .dll files. "**/*.dll" // Ignore all .dll files ], // flagWords - list of words to be always considered incorrect // This is useful for offensive words and common spelling errors. // For example "hte" should be "the"` "cSpell.flagWords": ["hte"], // Set the delay before spell checking the document. Default is 50. "cSpell.spellCheckDelayMs": 50, ``` ## Dictionaries The spell checker includes a set of default dictionaries. ### General Dictionaries - **wordsEn** - Derived from Hunspell US English words. - **wordsEnGb** - Derived from Hunspell GB English words. - **companies** - List of well known companies - **softwareTerms** - Software Terms and concepts like "coroutine", "debounce", "tree", etc. - **misc** - Terms that do not belong in the other dictionaries. ### Programming Language Dictionaries - **typescript** - keywords for Typescript and Javascript - **node** - terms related to using nodejs. - **php** - *php* keywords and library methods - **go** - *go* keywords and library methods - **python** - *python* keywords - **powershell** - *powershell* keywords - **html** - *html* related keywords - **css** - *css*, *less*, and *scss* related keywords ### Miscellaneous Dictionaries - **fonts** - long list of fonts - to assist with *css* Based upon the programming language, different dictionaries will be loaded. Here are the default rules: "*" matches any language. `"local"` is used to filter based upon the `"cSpell.language"` setting. ``` javascript { "cSpell.languageSettings": [ { "languageId": '*', "local": 'en', "dictionaries": ['wordsEn'] }, { "languageId": '*', "local": 'en-US', "dictionaries": ['wordsEn'] }, { "languageId": '*', "local": 'en-GB', "dictionaries": ['wordsEnGb'] }, { "languageId": '*', "dictionaries": ['companies', 'softwareTerms', 'misc'] }, { "languageId": "python", "allowCompoundWords": true, "dictionaries": ["python"]}, { "languageId": "go", "allowCompoundWords": true, "dictionaries": ["go"] }, { "languageId": "javascript", "dictionaries": ["typescript", "node"] }, { "languageId": "javascriptreact", "dictionaries": ["typescript", "node"] }, { "languageId": "typescript", "dictionaries": ["typescript", "node"] }, { "languageId": "typescriptreact", "dictionaries": ["typescript", "node"] }, { "languageId": "html", "dictionaries": ["html", "fonts", "typescript", "css"] }, { "languageId": "php", "dictionaries": ["php", "html", "fonts", "css", "typescript"] }, { "languageId": "css", "dictionaries": ["fonts", "css"] }, { "languageId": "less", "dictionaries": ["fonts", "css"] }, { "languageId": "scss", "dictionaries": ["fonts", "css"] }, ]; } ``` ### How to add your own Dictionaries #### Global Dictionary To add a global dictionary, you will need change your user settings. ##### Define the Dictionary In your user settings, you will need to tell the spell checker where to find your word list. Example adding medical terms, so words like *acanthopterygious* can be found. ```javascript // A List of Dictionary Definitions. "cSpell.dictionaryDefinitions": [ { "name": "medicalTerms", "path": "/Users/guest/projects/cSpell-WordLists/dictionaries/medicalterms-en.txt"} ], // List of dictionaries to use when checking files. "cSpell.dictionaries": [ "medicalTerms" ] ``` **Explained:** In this example, we have told the spell checker where to find the word list file. Since it is in the user settings, we have to use absolute paths. Once the dictionary is defined. We need to tell the spell checker when to use it. Adding it to `cSpell.dictionaries` advises the spell checker to always include the medical terms when spell checking. **Note:** Adding large dictionary files to be always used will slow down the generation of suggestions. #### Project / Workspace Dictionary To add a dictionary at the project level, it needs to be in the **cSpell.json** file. This file can be either at the project root or in the .vim directory. Example adding medical terms, where the terms are checked into the project and we only want to use it for .md files. ```javascript { "dictionaryDefinitions": [ { "name": "medicalTerms", "path": "./dictionaries/medicalterms-en.txt"}, { "name": "cities", "path": "./dictionaries/cities.txt"} ], "dictionaries": [ "cities" ], "languageSettings": [ { "languageId": "markdown", "dictionaries": ["medicalTerms"] }, { "languageId": "plaintext", "dictionaries": ["medicalTerms"] } ] } ``` **Explained:** In this example, two dictionaries were defined: *cities* and *medicalTerms*. The paths are relative to the location of the *cSpell.json* file. This allows for dictionaries to be checked into the project. The *cities* dictionary is used for every file type, because it was added to the list to *dictionaries*. The *medicalTerms* dictionary is only used when editing *markdown* or *plaintext* files. ## FAQ See: [FAQ](https://github.com/iamcco/coc-spell-checker/blob/master/FAQ.md) ### Buy Me A Coffee ☕️ ![btc](https://img.shields.io/keybase/btc/iamcco.svg?style=popout-square) ![image](https://user-images.githubusercontent.com/5492542/42771079-962216b0-8958-11e8-81c0-520363ce1059.png) ================================================ FILE: cspell.code-snippets ================================================ { "words": { "prefix": "spell words", "scope": "", "body": "${LINE_COMMENT} cspell:words ${1:word} ${2:word}", "description": "Words to be allowed in the document" }, "ignore": { "prefix": "spell ignore words", "scope": "", "body": "${LINE_COMMENT} cspell:ignore ${1:word} ${2:word}", "description": "Words to be ignored in the document" }, "ignore regexp": { "prefix": "spell ignore regexp", "scope": "", "body": "${LINE_COMMENT} cspell:ignoreRegExp /${1:expression}/", "description": "Ignore text matching the regular expression." }, "disable next line": { "prefix": "spell disable next line", "scope": "", "body": "${LINE_COMMENT} cspell:disable-next-line", "description": "Do not spell check the next line" }, "disable current line": { "prefix": "spell disable line", "scope": "", "body": "${LINE_COMMENT} cspell:disable-line", "description": "Do not spell check the current line" }, "disable spell checker": { "prefix": "spell disable spell checker", "scope": "", "body": "${LINE_COMMENT} cspell:disable", "description": "Disable spell checking from this point further." }, "enable spell checker": { "prefix": "spell enable spell checker", "scope": "", "body": "${LINE_COMMENT} cspell:enable", "description": "Enable spell checking from this point further." } } ================================================ FILE: dicts/README.md ================================================ # Dict bundle with extensions ================================================ FILE: dicts/vim/CHANGELOG.md ================================================ # Change Log - add vim help tag ================================================ FILE: dicts/vim/README.md ================================================ # Cspell Vim Language Dictionary Vim Language dictionary for cspell. This is a pre-built dictionary for use with cspell. Supports keywords of (Neo)vim This dictionary is included by default in cSpell. ## Installation Global Install and add to cspell global settings. ```sh npm install -g cspell-dict-vimlang cspell-dict-vimlang-link ``` ## Uninstall from cspell ```sh cspell-dict-vimlang-unlink ``` ## Manual Installation The `cspell-ext.json` file in this package should be added to the import section in your cspell.json file. ```javascript { // … "import": ["/cspell-dict-vimlang/cspell-ext.json"], // … } ``` ## Building I's only necessary if you want to modify the contents of the dictionary. Note: Building will take a few minutes for large files. ```sh npm run build ``` ================================================ FILE: dicts/vim/build-dict.js ================================================ #!/usr/bin/env node const {join} = require("path") const {existsSync, readFileSync, writeFileSync} = require("fs") const doc = process.argv.slice(2) if (doc.length === 0) { console.log('do not have files'); return } const dict = new Set() doc.forEach(rtp => { const tagPath = join(rtp, 'doc', 'tags') if (existsSync(tagPath)) { const tags = readFileSync(tagPath).toString().split('\n') tags.forEach(line => { line = line.trim() firstSection = line.split(' ')[0] // delete first `xxxx:` firstSection = firstSection.replace(/^[^:]:/, '') const words = firstSection.split(/[^a-zA-Z]/g) words.forEach(word => { word = word.replace(/[^a-zA-Z]/g, '') if (word.length > 2) { dict.add(word) } }) }) } else { console.log(`file ${tagPath} does not exists`) } }) let content = '' for (let word of dict) { if (content !== '') { content += ('\n' + word) } else { content = word } } console.log(`write to ${join(__dirname, 'vim.txt')}`); writeFileSync(join(__dirname, 'vim.txt'), content) ================================================ FILE: dicts/vim/cspell-ext.json ================================================ // cSpell Settings { "id": "vimlang", "name": "vim script Language", "description": "Go Language Dictionary", // List of dictionary files to add to the global list of dictionaries "dictionaryDefinitions": [ { "name": "vimlang", "file": "./vim.txt.gz", "description": "Vim Script Language Dictionary" } ], // Dictionaries to always be used. // Generally left empty "dictionaries": [], // Language Rules to apply to matching files. // Files are matched on `languageId` and `local` "languageSettings": [ { // VSCode languageId. i.e. typescript, java, go, cpp, javascript, markdown, latex // * will match against any file type. "languageId": "vim", // Language local. i.e. en-US, de-AT, or ru. * will match all locals. // Multiple locals can be specified like: "en, en-US" to match both English and English US. "local": "*", // It is common in vim to glue words together. "allowCompoundWords": true, // By default the whole text of a file is included for spell checking // Adding patterns to the "includeRegExpList" to only include matching patterns "includeRegExpList": [], // To exclude patterns, add them to "ignoreRegExpList" "ignoreRegExpList": [], // regex patterns than can be used with ignoreRegExpList or includeRegExpList // Example: "pattern": [{ "name": "mdash", "pattern": "—" }] // This could be included in "ignoreRegExpList": ["mdash"] "patterns": [], // List of dictionaries to enable by name in `dictionaryDefinitions` "dictionaries": ["vimlang"], // Dictionary definitions can also be supplied here. They are only used iff "languageId" and "local" match. "dictionaryDefinitions": [] } ] } ================================================ FILE: dicts/vim/index.d.ts ================================================ export function getConfigLocation(): string; ================================================ FILE: dicts/vim/index.js ================================================ 'use strict'; var util = require('./util'); function getConfigLocation() { return util.configLocation; } module.exports = { getConfigLocation, }; ================================================ FILE: dicts/vim/link.js ================================================ #!/usr/bin/env node 'use strict'; var util = require('./util'); util.install(); ================================================ FILE: dicts/vim/package.json ================================================ { "name": "cspell-dict-vimlang", "version": "1.0.1", "description": "Vim Language dictionary for cspell.", "bin": { "cspell-dict-vimlang-link": "./link.js", "cspell-dict-vimlang-unlink": "./unlink.js" }, "scripts": { "build": "./node_modules/.bin/cspell-tools compile \"vim.txt\" -o .", "test": "head -n 100 \"vim.txt\" | cspell -v -c ./cspell-ext.json --local=* --languageId=vim stdin", "cspell-link": "node link.js", "cspell-unlink": "node unlink.js" }, "repository": { "type": "git", "url": "git+https://github.com/iamcco/coc-spell-checker.git" }, "keywords": [ "cspell", "vimlang", "vim script language", "dictionary", "spelling" ], "main": "index.js", "typings": "index.d.ts", "author": "iamcco ", "license": "MIT", "bugs": { "url": "https://github.com/streetsidesoftware/cspell-dicts/issues" }, "homepage": "https://github.com/iamcco/coc-spell-checker/packages/README.md", "devDependcies": { "cspell-tools": "*" }, "dependencies": { "configstore": "^5.0.0" }, "files": [ "vim.txt.gz", "cspell-ext.json", "*.js", "*.d.ts" ] } ================================================ FILE: dicts/vim/unlink.js ================================================ #!/usr/bin/env node 'use strict'; var util = require('./util'); util.uninstall(); ================================================ FILE: dicts/vim/util.js ================================================ 'use strict'; // Cspell:word configstore var Configstore = require('configstore'); var Path = require('path'); var packageName = 'cspell'; var importPath = 'import'; var configLocation = Path.join(__dirname, 'cspell-ext.json'); function getImports(conf) { var imports = conf.get(importPath); imports = imports || []; if (typeof imports === 'string') { imports = [imports]; } return imports; } function install() { var conf = new Configstore(packageName); /** @type {string[]|string|undefined} */ var imports = getImports(conf); if (imports.indexOf(configLocation) < 0) { imports.push(configLocation); conf.set(importPath, imports); } } function uninstall() { var conf = new Configstore(packageName); /** @type {string[]|string|undefined} */ var imports = getImports(conf); var index = imports.indexOf(configLocation); if (index >= 0) { imports.splice(index, 1); conf.set(importPath, imports); } } module.exports = { install, uninstall, configLocation, }; ================================================ FILE: package.json ================================================ { "name": "coc-spell-checker", "description": "Spelling checker for source code", "keywords": [ "coc.nvim", "spell", "checker", "spellchecker", "multi-root ready" ], "license": "MIT", "version": "1.3.2", "publisher": "iamcco", "repository": { "type": "git", "url": "https://github.com/iamcco/coc-spell-checker" }, "bugs": { "url": "https://github.com/iamcco/coc-spell-checker/issues" }, "homepage": "https://github.com/iamcco/coc-spell-checker", "engines": { "coc": "^0.0.74" }, "activationEvents": [ "*" ], "main": "./out/index.js", "contributes": { "commands": [ { "command": "cSpell.addWordToWorkspaceDictionary", "title": "Add Word to Workspace Dictionary" }, { "command": "cSpell.addWordToDictionary", "title": "Add Word to Folder Dictionary" }, { "command": "cSpell.addWordToUserDictionary", "title": "Add Word to User Dictionary" }, { "command": "cSpell.enableForWorkspace", "title": "Enable Spell Checking For Workspace" }, { "command": "cSpell.disableForWorkspace", "title": "Disable Spell Checking For Workspace" }, { "command": "cSpell.enableLanguage", "title": "Enable Spell Checking For Specify Document Language" }, { "command": "cSpell.disableLanguage", "title": "Disable Spell Checking For Specify Document Language" }, { "command": "cSpell.enableCurrentLanguage", "title": "Enable Spell Checking Document Language" }, { "command": "cSpell.disableCurrentLanguage", "title": "Disable Spell Checking Document Language" }, { "command": "cSpell.toggleEnableSpellChecker", "title": "Toggle Spell Checking For the Current Workspace" }, { "command": "cSpell.removeWordFromFolderDictionary", "title": "Remove Words from the Folder Dictionary" }, { "command": "cSpell.removeWordFromWorkspaceDictionary", "title": "Remove Words from the Workspace Dictionary" }, { "command": "cSpell.removeWordFromUserDictionary", "title": "Remove Words from the Global Dictionary" }, { "command": "cSpell.addIgnoreWord", "title": "Ignore Word" }, { "command": "cSpell.addIgnoreWordToFolder", "title": "Ignore Word in Folder Settings" }, { "command": "cSpell.addIgnoreWordToWorkspace", "title": "Ignore Word in Workspace Settings" }, { "command": "cSpell.addIgnoreWordToUser", "title": "Ignore Word in User Settings" } ], "languages": [ { "id": "jsonc", "extensions": [ "cspell-ext.json", "cspell-default.json", "cspell.json", "cSpell.json" ] } ], "jsonValidation": [ { "fileMatch": "cSpell.json", "url": "https://raw.githubusercontent.com/streetsidesoftware/cspell/cspell%404.0.47/cspell.schema.json" }, { "fileMatch": "cspell.json", "url": "https://raw.githubusercontent.com/streetsidesoftware/cspell/cspell%404.0.47/cspell.schema.json" }, { "fileMatch": "cspell-default.json", "url": "https://raw.githubusercontent.com/streetsidesoftware/cspell/cspell%404.0.47/cspell.schema.json" }, { "fileMatch": "cspell-ext.json", "url": "https://raw.githubusercontent.com/streetsidesoftware/cspell/cspell%404.0.47/cspell.schema.json" } ], "configuration": { "type": "object", "title": "Code Spell Checker Configuration", "properties": { "cSpell.trace.server": { "type": "string", "default": "off", "enum": [ "off", "messages", "verbose" ], "description": "Trace level of log" }, "cSpell.language": { "type": "string", "scope": "resource", "default": "en", "description": "The Language local to use when spell checking. \"en\" and \"en-GB\" are currently supported." }, "cSpell.maxNumberOfProblems": { "type": "number", "scope": "resource", "default": 100, "description": "Controls the maximum number of spelling errors per document." }, "cSpell.checkLimit": { "type": "number", "scope": "resource", "default": 500, "description": "The limit in K-Bytes to be checked in a file." }, "cSpell.numSuggestions": { "type": "number", "scope": "resource", "default": 8, "description": "Controls the number of suggestions shown." }, "cSpell.minWordLength": { "type": "number", "scope": "resource", "default": 4, "description": "The minimum length of a word before checking it against a dictionary." }, "cSpell.maxDuplicateProblems": { "type": "number", "scope": "resource", "default": 5, "description": "The maximum number of times the same word can be flagged as an error in a file." }, "cSpell.enabledLanguageIds": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [ "vim", "asciidoc", "c", "cpp", "csharp", "css", "git-commit", "gitcommit", "go", "handlebars", "haskell", "html", "jade", "java", "javascript", "javascriptreact", "json", "jsonc", "latex", "less", "markdown", "php", "plaintext", "python", "pug", "restructuredtext", "rust", "scala", "scss", "text", "typescript", "typescriptreact", "yaml", "yml" ], "description": "Specify file types to spell check." }, "cSpell.import": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [], "description": "List of paths of cspell.json files to import." }, "cSpell.words": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [], "description": "Words to add to dictionary for a workspace." }, "cSpell.userWords": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [], "description": "User words to add to dictionary. Should only be in the user settings." }, "cSpell.ignoreWords": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [], "description": "A list of words to be ignored by the spell checker." }, "cSpell.enabled": { "type": "boolean", "scope": "resource", "default": true, "description": "Enable / Disable the spell checker." }, "cSpell.diagnosticLevel": { "type": "string", "scope": "resource", "enum": [ "Error", "Warning", "Information", "Hint" ], "default": "Information", "description": "Issues found by the spell checker are marked with a Diagnostic Severity Level. This affects the color of squiggle." }, "cSpell.showStatus": { "type": "boolean", "scope": "resource", "default": true, "description": "Display the spell checker status on the status bar." }, "cSpell.spellCheckDelayMs": { "type": "number", "default": 50, "description": "Delay in ms after a document has changed before checking it for spelling errors." }, "cSpell.ignorePaths": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [ "**/package-lock.json", "**/node_modules/**", "**/vscode-extension/**", "**/.git/objects/**", ".vscode" ], "description": "Specify paths/files to ignore. (Supports Globs)" }, "cSpell.flagWords": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [], "description": "Words to always be flagged as an error." }, "cSpell.patterns": { "type": "array", "scope": "resource", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "The pattern identifier." }, "pattern": { "type": "string", "description": "Regular expression, default flags are: 'gim'" } }, "description": "Define a pattern" }, "default": [], "description": "Defines a list of named regular expression patterns that can be used in exclusion or inclusion lists." }, "cSpell.ignoreRegExpList": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [], "description": "List of regular expressions used to exclude the matching text from being checked.\n Example: \"0x[a-f0-9]+\" to skip 0x hex values.\n By default the flags are 'gim'.\n You can specify them like this:\n \"/0x[A-F0-9]/g\" to match only upper case hex numbers.\n Example to match email: \"?\" " }, "cSpell.includeRegExpList": { "type": "array", "scope": "resource", "items": { "type": "string" }, "default": [], "description": "List of regular expressions used to include text to be spell checked.\nBy default, all text is checked. Adding regular expresses to this list will limit the text to be spell checked to only text that matches any of the expressions in the list.\nLogic: text to be checked = include - exclude\nNote: Slashes need to be double: \\\\ because it is in a json string. \nExamples:\n* \".*\" -- include everything.\n* \"'(?:[^'\\\\n]|\\\\\\\\')*'\" -- single quote strings. \n" }, "cSpell.allowCompoundWords": { "type": "boolean", "scope": "resource", "default": false, "description": "Enable / Disable allowing word compounds. true means 'arraylength' would be ok, false means it would not pass." }, "cSpell.languageSettings": { "type": "array", "scope": "resource", "description": "Define settings on a per programming language basis.", "items": { "$ref": "https://raw.githubusercontent.com/streetsidesoftware/cspell/cspell%404.0.47/cspell.schema.json#/definitions/LanguageSetting" } }, "cSpell.dictionaries": { "type": "array", "scope": "resource", "description": "List of dictionaries to use when checking files.", "items": { "type": "string", "description": "Name of dictionary to use." } }, "cSpell.dictionaryDefinitions": { "type": "array", "scope": "resource", "description": "A List of Dictionary Definitions.", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Name of dictionary" }, "path": { "type": "string", "description": "Path to the dictionary file. Relative paths will be relative to the workspace." } }, "required": [ "name", "path" ] } }, "cSpell.showCommandsInEditorContextMenu": { "type": "boolean", "scope": "application", "description": "Show Spell Checker actions in Editor Context Menu", "default": true }, "cSpell.fixSpellingWithRenameProvider": { "type": "boolean", "scope": "application", "description": "Experimental: Use Rename when fixing spelling issues.", "default": false }, "cSpell.logLevel": { "type": "string", "scope": "window", "description": "Set the Debug Level for logging messages.", "enum": [ "None", "Error", "Warning", "Information", "Debug" ], "default": "Error" }, "cSpell.allowedSchemas": { "type": "array", "scope": "window", "description": "Control which file schemas will be checked for spelling (VS Code must be restarted for this setting to take effect).", "items": { "type": "string" }, "default": [ "file", "untitled" ] }, "cSpell.overrides": { "type": "array", "description": "Overrides to apply based upon the file path.", "items": { "$ref": "https://raw.githubusercontent.com/streetsidesoftware/cspell/cspell%404.0.47/cspell.schema.json#/definitions/OverrideSettings" } }, "cSpell.status-bar-text": { "type": "string", "description": "Text show in status bar, default cSpell", "default": "cSpell" } } } }, "scripts": { "clean": "rm -rf ./out", "clean:server": "rm -rf ./server", "clean:all": "npm-run-all clean:server clean", "build": "webpack", "build:server": "git clone https://github.com/streetsidesoftware/vscode-spell-checker && cd vscode-spell-checker/packages/_server && yarn && yarn compile && mv ../client/server ../../../ && cd ../../../ && rm -rf vscode-spell-checker", "build:all": "npm-run-all build:server build", "prepublishOnly": "npm-run-all clean:all build:all", "prepare": "npm-run-all clean build" }, "devDependencies": { "@types/comment-json": "^1.1.1", "@types/fs-extra": "^8.1.0", "@types/node": "^12.12.27", "coc.nvim": "^0.0.74", "lorem-ipsum": "^1.0.6", "npm-run-all": "4.1.5", "rimraf": "^3.0.2", "ts-loader": "^6.2.1", "tslint": "^5.20.1", "typescript": "^3.7.5", "vscode-languageserver-protocol": "^3.14.1", "webpack": "^4.41.6", "webpack-cli": "^3.3.11" }, "dependencies": { "comment-json": "^1.1.3", "cspell-dict-vimlang": "^1.0.1", "cspell-glob": "^0.1.17", "cspell-lib": "^4.1.16", "fs-extra": "^8.1.0", "gensequence": "^2.3.0", "iconv-lite": "^0.4.24", "micromatch": "^4.0.2", "minimatch": "^3.0.4", "node-watch": "^0.6.3", "rxjs": "^6.5.4", "vscode-jsonrpc": "^4.0.0", "vscode-languageserver": "^5.2.1", "vscode-uri": "^2.1.1" } } ================================================ FILE: server/SuggestionsGenerator.d.ts ================================================ import { CSpellUserSettings } from './cspellConfig'; import { SuggestionResult } from 'cspell-lib'; import { SpellingDictionaryCollection } from 'cspell-lib/dist/SpellingDictionary'; export declare const maxWordLengthForSuggestions = 20; export declare const wordLengthForLimitingSuggestions = 15; export declare const maxNumberOfSuggestionsForLongWords = 1; export interface GetSettingsResult { settings: CSpellUserSettings; dictionary: SpellingDictionaryCollection; } export declare class SuggestionGenerator { readonly getSettings: (doc: DocInfo) => GetSettingsResult | Promise; constructor(getSettings: (doc: DocInfo) => GetSettingsResult | Promise); genSuggestions(doc: DocInfo, word: string): Promise; genWordSuggestions(doc: DocInfo, word: string): Promise; } ================================================ FILE: server/SuggestionsGenerator.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const cspell_lib_1 = require("cspell-lib"); const defaultNumSuggestions = 10; const regexJoinedWords = /[+]/g; exports.maxWordLengthForSuggestions = 20; exports.wordLengthForLimitingSuggestions = 15; exports.maxNumberOfSuggestionsForLongWords = 1; const maxEdits = 3; class SuggestionGenerator { constructor(getSettings) { this.getSettings = getSettings; } async genSuggestions(doc, word) { const { settings, dictionary } = await this.getSettings(doc); const { numSuggestions = defaultNumSuggestions } = settings; if (word.length > exports.maxWordLengthForSuggestions) { return []; } const numSugs = word.length > exports.wordLengthForLimitingSuggestions ? exports.maxNumberOfSuggestionsForLongWords : numSuggestions; const options = { numChanges: maxEdits, numSuggestions: numSugs, // Turn off compound suggestions for now until it works a bit better. compoundMethod: cspell_lib_1.CompoundWordsMethod.NONE, ignoreCase: !settings.caseSensitive, }; return dictionary.suggest(word, options).map(s => (Object.assign(Object.assign({}, s), { word: s.word.replace(regexJoinedWords, '') }))); } async genWordSuggestions(doc, word) { return (await this.genSuggestions(doc, word)).map(sr => sr.word); } } exports.SuggestionGenerator = SuggestionGenerator; //# sourceMappingURL=SuggestionsGenerator.js.map ================================================ FILE: server/SuggestionsGenerator.test.d.ts ================================================ export {}; ================================================ FILE: server/SuggestionsGenerator.test.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const cspell = require("cspell-lib"); const SuggestionsGenerator_1 = require("./SuggestionsGenerator"); describe('Validate Suggestions', () => { test('genWordSuggestions', async () => { const gen = new SuggestionsGenerator_1.SuggestionGenerator(getSettings); const doc = { languageId: 'typescript', text: '' }; const { settings } = await getSettings(doc); const result = await gen.genWordSuggestions(doc, 'code'); expect(result).toContain('code'); expect(result).toHaveLength(settings.numSuggestions || 0); }); test('genWordSuggestions for long words', async () => { const gen = new SuggestionsGenerator_1.SuggestionGenerator(getSettings); const doc = { languageId: 'typescript', text: '' }; const result = await gen.genWordSuggestions(doc, 'Acknowledgements'); expect(result).toHaveLength(SuggestionsGenerator_1.maxNumberOfSuggestionsForLongWords); expect(result).toContain('acknowledgements'); }); async function getSettings(doc) { const settings = await cspell.constructSettingsForText(cspell.getDefaultSettings(), doc.text || '', doc.languageId); const dictionary = await cspell.getDictionary(settings); return { settings, dictionary }; } }); //# sourceMappingURL=SuggestionsGenerator.test.js.map ================================================ FILE: server/api.d.ts ================================================ import * as config from './cspellConfig'; export interface GetConfigurationForDocumentResult { languageEnabled: boolean | undefined; fileEnabled: boolean | undefined; settings: config.CSpellUserSettings | undefined; docSettings: config.CSpellUserSettings | undefined; } export interface IsSpellCheckEnabledResult { languageEnabled: boolean | undefined; fileEnabled: boolean | undefined; } export interface SplitTextIntoWordsResult { words: string[]; } export interface SpellingSuggestionsResult { } export interface TextDocumentInfo { uri?: string; languageId?: string; text?: string; } export declare type ServerRequestMethods = keyof ServerMethodRequestResult; export declare type ServerRequestMethodConstants = { [key in ServerRequestMethods]: key; }; declare type ServerRequestResult = { request: Req; result: Res; }; export declare type ServerMethodRequestResult = { getConfigurationForDocument: ServerRequestResult; isSpellCheckEnabled: ServerRequestResult; splitTextIntoWords: ServerRequestResult; spellingSuggestions: ServerRequestResult; }; export declare type ServerRequestMethodResults = { [key in keyof ServerMethodRequestResult]: ServerMethodRequestResult[key]['result']; }; export declare type ServerRequestMethodRequests = { [key in keyof ServerMethodRequestResult]: ServerMethodRequestResult[key]['request']; }; export declare type NotifyServerMethods = 'onConfigChange' | 'registerConfigurationFile'; export declare type NotifyServerMethodConstants = { [key in NotifyServerMethods]: NotifyServerMethods; }; export {}; ================================================ FILE: server/api.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); //# sourceMappingURL=api.js.map ================================================ FILE: server/autoLoad.d.ts ================================================ export interface AutoLoadCache { (key: K): T; get: (key: K) => T; clear: () => void; has: (key: K) => boolean; delete: (key: K) => void; } export declare function createAutoLoadCache(loader: (key: K) => T): AutoLoadCache; export interface LazyValue { (): T; get: () => T; clear: () => void; } export declare function createLazyValue(loader: () => T): LazyValue; ================================================ FILE: server/autoLoad.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function createAutoLoadCache(loader) { const cache = new Map(); const getter = ((key) => { const found = cache.get(key); if (found) return found; const value = loader(key); cache.set(key, value); return value; }); getter.get = getter; getter.has = (key) => cache.has(key); getter.delete = (key) => cache.delete(key); getter.clear = () => cache.clear(); return getter; } exports.createAutoLoadCache = createAutoLoadCache; const notSet = Symbol('Value Not Set'); function createLazyValue(loader) { let v = notSet; const getter = (() => { if (v === notSet) { v = loader(); } return v; }); getter.clear = () => v = notSet; getter.get = getter; return getter; } exports.createLazyValue = createLazyValue; //# sourceMappingURL=autoLoad.js.map ================================================ FILE: server/autoLoad.test.d.ts ================================================ export {}; ================================================ FILE: server/autoLoad.test.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const autoLoad_1 = require("./autoLoad"); describe('Validate AutoLoadCache', () => { test('create', () => { let n = 0; const loader = (key) => key + n++; const cache = autoLoad_1.createAutoLoadCache(loader); expect(cache(5)).toEqual(5 + n - 1); expect(n).toEqual(1); expect(cache.has(5)).toBe(true); expect(cache.has(4)).toBe(false); expect(cache(4)).toEqual(4 + n - 1); expect(n).toEqual(2); expect(cache(5)).toEqual(5 + n - 2); expect(n).toEqual(2); cache.delete(5); expect(cache(5)).toEqual(5 + n - 1); expect(n).toEqual(3); cache.clear(); expect(cache).toEqual(cache.get); }); }); describe('Validate LazyValue', () => { test('', () => { let n = 0; const loader = () => n++; const value = autoLoad_1.createLazyValue(loader); expect(n).toEqual(0); expect(value()).toEqual(0); expect(value()).toEqual(0); value.clear(); expect(value()).toEqual(1); }); }); //# sourceMappingURL=autoLoad.test.js.map ================================================ FILE: server/codeActions.d.ts ================================================ import { TextDocument, TextDocuments, CodeActionParams } from 'vscode-languageserver'; import { CodeAction } from 'vscode-languageserver-types'; import { CSpellUserSettings } from './cspellConfig'; import { DocumentSettings } from './documentSettings'; export declare function onCodeActionHandler(documents: TextDocuments, fnSettings: (doc: TextDocument) => Promise, fnSettingsVersion: (doc: TextDocument) => number, documentSettings: DocumentSettings): (params: CodeActionParams) => Promise; ================================================ FILE: server/codeActions.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const LangServer = require("vscode-languageserver"); const cspell_lib_1 = require("cspell-lib"); const Validator = require("./validator"); const cspell = require("cspell-lib"); const documentSettings_1 = require("./documentSettings"); const SuggestionsGenerator_1 = require("./SuggestionsGenerator"); const util_1 = require("./util"); function extractText(textDocument, range) { const { start, end } = range; const offStart = textDocument.offsetAt(start); const offEnd = textDocument.offsetAt(end); return textDocument.getText().slice(offStart, offEnd); } function onCodeActionHandler(documents, fnSettings, fnSettingsVersion, documentSettings) { const sugGen = new SuggestionsGenerator_1.SuggestionGenerator(getSettings); const settingsCache = new Map(); async function getSettings(doc) { const cached = settingsCache.get(doc.uri); const settingsVersion = fnSettingsVersion(doc); if (!cached || cached.docVersion !== doc.version || cached.settingsVersion !== settingsVersion) { const settings = constructSettings(doc); settingsCache.set(doc.uri, { docVersion: doc.version, settings, settingsVersion }); } return settingsCache.get(doc.uri).settings; } async function constructSettings(doc) { const settings = cspell.constructSettingsForText(await fnSettings(doc), doc.getText(), doc.languageId); const dictionary = await cspell.getDictionary(settings); return { settings, dictionary }; } const handler = async (params) => { const actions = []; const { context, textDocument: { uri } } = params; const { diagnostics } = context; const spellCheckerDiags = diagnostics.filter(diag => diag.source === Validator.diagSource); if (!spellCheckerDiags.length) return []; const optionalTextDocument = documents.get(uri); if (!optionalTextDocument) return []; util_1.log(`CodeAction Only: ${context.only} Num: ${diagnostics.length}`, uri); const textDocument = optionalTextDocument; const { settings: docSetting, dictionary } = await getSettings(textDocument); if (!documentSettings_1.isUriAllowed(uri, docSetting.allowedSchemas)) { util_1.log(`CodeAction Uri Not allowed: ${uri}`); return []; } const folders = await documentSettings.folders; const showAddToWorkspace = folders && folders.length > 1; const showAddToFolder = folders && folders.length > 0; function replaceText(range, text) { return LangServer.TextEdit.replace(range, text || ''); } function getSuggestions(word) { return sugGen.genWordSuggestions(textDocument, word); } function createAction(title, command, diags, ...args) { const cmd = LangServer.Command.create(title, command, ...args); const action = LangServer.CodeAction.create(title, cmd); action.diagnostics = diags; action.kind = LangServer.CodeActionKind.QuickFix; return action; } async function genCodeActionsForSuggestions(_dictionary) { util_1.log('CodeAction generate suggestions'); let diagWord; for (const diag of spellCheckerDiags) { const word = extractText(textDocument, diag.range); diagWord = diagWord || word; const sugs = await getSuggestions(word); sugs .map(sug => cspell_lib_1.Text.isLowerCase(sug) ? cspell_lib_1.Text.matchCase(word, sug) : sug) .filter(util_1.uniqueFilter()) .forEach(sugWord => { const action = createAction(sugWord, 'cSpell.editText', [diag], uri, textDocument.version, [replaceText(diag.range, sugWord)]); /** * Waiting on [Add isPreferred to the CodeAction protocol. Pull Request #489 · Microsoft/vscode-languageserver-node](https://github.com/Microsoft/vscode-languageserver-node/pull/489) * Note we might want this to be a config setting incase someone has `"editor.codeActionsOnSave": { "source.fixAll": true }` * if (!actions.length) { * action.isPreferred = true; * } */ actions.push(action); }); } const word = diagWord || extractText(textDocument, params.range); // Only suggest adding if it is our diagnostic and there is a word. if (word && spellCheckerDiags.length) { actions.push(createAction('Add: "' + word + '" to user dictionary', 'cSpell.addWordToUserDictionarySilent', spellCheckerDiags, word, textDocument.uri)); if (showAddToFolder) { // Allow the them to add it to the project dictionary. actions.push(createAction('Add: "' + word + '" to folder dictionary', 'cSpell.addWordToDictionarySilent', spellCheckerDiags, word, textDocument.uri)); } if (showAddToWorkspace) { // Allow the them to add it to the workspace dictionary. actions.push(createAction('Add: "' + word + '" to workspace dictionary', 'cSpell.addWordToWorkspaceDictionarySilent', spellCheckerDiags, word, textDocument.uri)); } } return actions; } return genCodeActionsForSuggestions(dictionary); }; return handler; } exports.onCodeActionHandler = onCodeActionHandler; //# sourceMappingURL=codeActions.js.map ================================================ FILE: server/cspellConfig.d.ts ================================================ import * as cspell from 'cspell-lib'; export { LanguageSetting, DictionaryDefinition } from 'cspell-lib'; export interface SpellCheckerSettings { checkLimit?: number; diagnosticLevel?: string; allowedSchemas?: string[]; logLevel?: 'None' | 'Error' | 'Warning' | 'Information' | 'Debug'; showStatus?: boolean; spellCheckDelayMs?: number; showCommandsInEditorContextMenu?: boolean; } export interface CSpellUserSettingsWithComments extends cspell.CSpellUserSettingsWithComments, SpellCheckerSettings { } export interface CSpellUserSettings extends cspell.CSpellSettings, SpellCheckerSettings { } ================================================ FILE: server/cspellConfig.js ================================================ "use strict"; // Export the cspell settings to the client. Object.defineProperty(exports, "__esModule", { value: true }); //# sourceMappingURL=cspellConfig.js.map ================================================ FILE: server/documentSettings.d.ts ================================================ import { Connection, TextDocumentUri } from './vscode.config'; import * as vscode from 'vscode-languageserver'; import { ExcludeFilesGlobMap, RegExpPatternDefinition, Pattern } from 'cspell-lib'; import { CSpellUserSettings } from './cspellConfig'; import { AutoLoadCache } from './autoLoad'; export interface SettingsCspell { cSpell?: CSpellUserSettings; } export interface SettingsVSCode { search?: { exclude?: ExcludeFilesGlobMap; }; } export declare class DocumentSettings { readonly connection: Connection; readonly defaultSettings: CSpellUserSettings; private cachedValues; readonly getUriSettings: AutoLoadCache>; private readonly fetchSettingsForUri; private readonly _cspellFileSettingsByFolderCache; private readonly fetchVSCodeConfiguration; private readonly _folders; readonly configsToImport: Set; private readonly importedSettings; private _version; constructor(connection: Connection, defaultSettings: CSpellUserSettings); getSettings(document: TextDocumentUri): Promise; _getUriSettings(uri: string): Promise; isExcluded(uri: string): Promise; resetSettings(): void; get folders(): Promise; private _importSettings; get version(): number; registerConfigurationFile(path: string): void; private fetchUriSettings; private findMatchingFolder; private fetchFolders; private _fetchVSCodeConfiguration; private fetchSettingsFromVSCode; private _fetchSettingsForUri; private matchingFoldersForUri; private createCache; private createLazy; private readSettingsForFolderUri; } declare function resolvePath(...parts: string[]): string; export declare function isUriAllowed(uri: string, schemes?: string[]): boolean; export declare function isUriBlackListed(uri: string, schemes?: string[]): boolean; export declare function doesUriMatchAnyScheme(uri: string, schemes: string[]): boolean; declare function fixRegEx(pat: Pattern): Pattern; declare function fixPattern(pat: RegExpPatternDefinition): RegExpPatternDefinition; export declare function correctBadSettings(settings: CSpellUserSettings): CSpellUserSettings; export declare const debugExports: { fixRegEx: typeof fixRegEx; fixPattern: typeof fixPattern; resolvePath: typeof resolvePath; }; export {}; ================================================ FILE: server/documentSettings.js ================================================ "use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); // cSpell:ignore pycache const vscode_config_1 = require("./vscode.config"); const path = require("path"); const fs = require("fs-extra"); const CSpell = require("cspell-lib"); const vscode_uri_1 = require("vscode-uri"); const util_1 = require("./util"); const autoLoad_1 = require("./autoLoad"); const cspell_glob_1 = require("cspell-glob"); const os = require("os"); const cSpellSection = 'cSpell'; const defaultExclude = [ '**/*.rendered', '**/*.*.rendered', '__pycache__/**', ]; const defaultAllowedSchemes = ['file', 'untitled']; const schemeBlackList = ['git', 'output', 'debug', 'vscode']; const defaultRootUri = vscode_uri_1.URI.file('').toString(); class DocumentSettings { constructor(connection, defaultSettings) { this.connection = connection; this.defaultSettings = defaultSettings; // Cache per folder settings this.cachedValues = []; this.getUriSettings = this.createCache((key = '') => this._getUriSettings(key)); this.fetchSettingsForUri = this.createCache((key) => this._fetchSettingsForUri(key)); this._cspellFileSettingsByFolderCache = this.createCache(_readSettingsForFolderUri); this.fetchVSCodeConfiguration = this.createCache((key) => this._fetchVSCodeConfiguration(key)); this._folders = this.createLazy(() => this.fetchFolders()); this.configsToImport = new Set(); this.importedSettings = this.createLazy(() => this._importSettings()); this._version = 0; } async getSettings(document) { return this.getUriSettings(document.uri); } async _getUriSettings(uri) { util_1.log('getUriSettings:', uri); const r = uri ? await this.fetchUriSettings(uri) : CSpell.mergeSettings(this.defaultSettings, this.importedSettings()); return r; } async isExcluded(uri) { const settings = await this.fetchSettingsForUri(uri); return settings.globMatcher.match(vscode_uri_1.URI.parse(uri).path); } resetSettings() { util_1.log('resetSettings'); CSpell.clearCachedSettings(); this.cachedValues.forEach(cache => cache.clear()); this._version += 1; } get folders() { return this._folders(); } _importSettings() { util_1.log('importSettings'); const importPaths = [...this.configsToImport.keys()].sort(); return readSettingsFiles(importPaths); } get version() { return this._version; } registerConfigurationFile(path) { util_1.log('registerConfigurationFile:', path); this.configsToImport.add(path); this.importedSettings.clear(); this.resetSettings(); } async fetchUriSettings(uri) { util_1.log('Start fetchUriSettings:', uri); const folderSettings = await this.fetchSettingsForUri(uri); const spellSettings = CSpell.mergeSettings(this.defaultSettings, this.importedSettings(), folderSettings.settings); const fileUri = vscode_uri_1.URI.parse(uri); const fileSettings = CSpell.calcOverrideSettings(spellSettings, fileUri.fsPath); util_1.log('Finish fetchUriSettings:', uri); return fileSettings; } async findMatchingFolder(docUri) { const root = vscode_uri_1.URI.parse(docUri || defaultRootUri).with({ path: '' }); return (await this.matchingFoldersForUri(docUri))[0] || { uri: root.toString(), name: 'root' }; } async fetchFolders() { return (await vscode_config_1.getWorkspaceFolders(this.connection)) || []; } async _fetchVSCodeConfiguration(uri) { return (await vscode_config_1.getConfiguration(this.connection, [ { scopeUri: uri || undefined, section: cSpellSection }, { section: 'search' } ])).map(v => v || {}); } async fetchSettingsFromVSCode(uri) { const configs = await this.fetchVSCodeConfiguration(uri || ''); const [cSpell, search] = configs; const { exclude = {} } = search; const { ignorePaths = [] } = cSpell; const cSpellConfigSettings = Object.assign(Object.assign({}, cSpell), { id: 'VSCode-Config', ignorePaths: ignorePaths.concat(CSpell.ExclusionHelper.extractGlobsFromExcludeFilesGlobMap(exclude)) }); return cSpellConfigSettings; } async _fetchSettingsForUri(uri) { util_1.log(`fetchFolderSettings: URI ${uri}`); const cSpellConfigSettings = await this.fetchSettingsFromVSCode(uri); const folder = await this.findMatchingFolder(uri); const cSpellFolderSettings = resolveConfigImports(cSpellConfigSettings, folder.uri); const settings = this.readSettingsForFolderUri(folder.uri); // cspell.json file settings take precedence over the vscode settings. const mergedSettings = CSpell.mergeSettings(cSpellFolderSettings, settings); const { ignorePaths = [] } = mergedSettings; const globs = defaultExclude.concat(ignorePaths); const root = vscode_uri_1.URI.parse(folder.uri).path; const globMatcher = new cspell_glob_1.GlobMatcher(globs, root); const ext = { uri, vscodeSettings: { cSpell: cSpellConfigSettings }, settings: mergedSettings, globMatcher, }; return ext; } async matchingFoldersForUri(docUri) { const folders = await this.folders; return folders .filter(({ uri }) => uri === docUri.slice(0, uri.length)) .sort((a, b) => a.uri.length - b.uri.length) .reverse(); } createCache(loader) { const cache = autoLoad_1.createAutoLoadCache(loader); this.cachedValues.push(cache); return cache; } createLazy(loader) { const lazy = autoLoad_1.createLazyValue(loader); this.cachedValues.push(lazy); return lazy; } readSettingsForFolderUri(folderUri) { return this._cspellFileSettingsByFolderCache.get(folderUri); } } exports.DocumentSettings = DocumentSettings; function configPathsForRoot(workspaceRootUri) { const workspaceRoot = workspaceRootUri ? vscode_uri_1.URI.parse(workspaceRootUri).fsPath : ''; const paths = workspaceRoot ? [ path.join(workspaceRoot, '.vscode', CSpell.defaultSettingsFilename.toLowerCase()), path.join(workspaceRoot, '.vscode', CSpell.defaultSettingsFilename), path.join(workspaceRoot, '.' + CSpell.defaultSettingsFilename.toLowerCase()), path.join(workspaceRoot, CSpell.defaultSettingsFilename.toLowerCase()), path.join(workspaceRoot, CSpell.defaultSettingsFilename), ] : []; return paths; } function resolveConfigImports(config, folderUri) { util_1.log('resolveConfigImports:', folderUri); const uriFsPath = vscode_uri_1.URI.parse(folderUri).fsPath; const imports = typeof config.import === 'string' ? [config.import] : config.import || []; const importAbsPath = imports.map(file => resolvePath(uriFsPath, file)); util_1.log(`resolvingConfigImports: [\n${imports.join('\n')}]`); util_1.log(`resolvingConfigImports ABS: [\n${importAbsPath.join('\n')}]`); const _a = importAbsPath.length ? CSpell.mergeSettings(readSettingsFiles([...importAbsPath]), config) : config, { import: _import } = _a, result = __rest(_a, ["import"]); return result; } function _readSettingsForFolderUri(folderUri) { return folderUri ? readSettingsFiles(configPathsForRoot(folderUri)) : {}; } function readSettingsFiles(paths) { util_1.log('readSettingsFiles:', paths); const existingPaths = paths.filter(filename => exists(filename)); util_1.log('readSettingsFiles actual:', existingPaths); return existingPaths.length ? CSpell.readSettingsFiles(existingPaths) : {}; } function exists(file) { try { const s = fs.statSync(file); return s.isFile(); } catch (e) { } return false; } function resolvePath(...parts) { const normalizedParts = parts.map(part => part[0] === '~' ? os.homedir() + part.slice(1) : part); return path.resolve(...normalizedParts); } function isUriAllowed(uri, schemes) { schemes = schemes || defaultAllowedSchemes; return doesUriMatchAnyScheme(uri, schemes); } exports.isUriAllowed = isUriAllowed; function isUriBlackListed(uri, schemes = schemeBlackList) { return doesUriMatchAnyScheme(uri, schemes); } exports.isUriBlackListed = isUriBlackListed; function doesUriMatchAnyScheme(uri, schemes) { const schema = vscode_uri_1.URI.parse(uri).scheme; return schemes.findIndex(v => v === schema) >= 0; } exports.doesUriMatchAnyScheme = doesUriMatchAnyScheme; const correctRegExMap = new Map([ ['/"""(.*?\\n?)+?"""/g', '/(""")[^\\1]*?\\1/g'], ["/'''(.*?\\n?)+?'''/g", "/(''')[^\\1]*?\\1/g"], ]); function fixRegEx(pat) { if (typeof pat != 'string') { return pat; } return correctRegExMap.get(pat) || pat; } function fixPattern(pat) { const pattern = fixRegEx(pat.pattern); if (pattern === pat.pattern) { return pat; } return Object.assign(Object.assign({}, pat), { pattern }); } function correctBadSettings(settings) { var _a, _b, _c, _d, _e, _f; const newSettings = Object.assign({}, settings); // Fix patterns newSettings.patterns = (_b = (_a = newSettings) === null || _a === void 0 ? void 0 : _a.patterns) === null || _b === void 0 ? void 0 : _b.map(fixPattern); newSettings.ignoreRegExpList = (_d = (_c = newSettings) === null || _c === void 0 ? void 0 : _c.ignoreRegExpList) === null || _d === void 0 ? void 0 : _d.map(fixRegEx); newSettings.includeRegExpList = (_f = (_e = newSettings) === null || _e === void 0 ? void 0 : _e.includeRegExpList) === null || _f === void 0 ? void 0 : _f.map(fixRegEx); return newSettings; } exports.correctBadSettings = correctBadSettings; exports.debugExports = { fixRegEx, fixPattern, resolvePath, }; //# sourceMappingURL=documentSettings.js.map ================================================ FILE: server/documentSettings.test.d.ts ================================================ export {}; ================================================ FILE: server/documentSettings.test.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const documentSettings_1 = require("./documentSettings"); const vscode_config_1 = require("./vscode.config"); const Path = require("path"); const vscode_uri_1 = require("vscode-uri"); const cspell = require("cspell-lib"); const os = require("os"); jest.mock('vscode-languageserver'); jest.mock('./vscode.config'); jest.mock('./util'); const mockGetWorkspaceFolders = vscode_config_1.getWorkspaceFolders; const mockGetConfiguration = vscode_config_1.getConfiguration; const workspaceRoot = Path.resolve(Path.join(__dirname, '..')); const workspaceFolder = { uri: vscode_uri_1.URI.file(workspaceRoot).toString(), name: '_server', }; describe('Validate DocumentSettings', () => { beforeEach(() => { // Clear all mock instances and calls to constructor and all methods: mockGetWorkspaceFolders.mockClear(); }); test('version', () => { const docSettings = newDocumentSettings(); expect(docSettings.version).toEqual(0); docSettings.resetSettings(); expect(docSettings.version).toEqual(1); }); it('checks isUriAllowed', () => { expect(documentSettings_1.isUriAllowed(vscode_uri_1.URI.file(__filename).toString())).toBe(true); }); it('checks isUriBlackListed', () => { const uriFile = vscode_uri_1.URI.file(__filename); expect(documentSettings_1.isUriBlackListed(uriFile.toString())).toBe(false); const uriGit = uriFile.with({ scheme: 'debug' }); expect(documentSettings_1.isUriBlackListed(uriGit.toString())).toBe(true); }); it('folders', async () => { const mockFolders = [workspaceFolder]; mockGetWorkspaceFolders.mockReturnValue(mockFolders); const docSettings = newDocumentSettings(); const folders = await docSettings.folders; expect(folders).toBe(mockFolders); }); it('tests register config path', () => { const mockFolders = [workspaceFolder]; mockGetWorkspaceFolders.mockReturnValue(mockFolders); const docSettings = newDocumentSettings(); const configFile = Path.resolve(Path.join(__dirname, '..', '..', '..', 'cSpell.json')); expect(docSettings.version).toEqual(0); docSettings.registerConfigurationFile(configFile); expect(docSettings.version).toEqual(1); expect(docSettings.configsToImport).toContain(configFile); }); it('test getSettings', async () => { const mockFolders = [workspaceFolder]; mockGetWorkspaceFolders.mockReturnValue(mockFolders); mockGetConfiguration.mockReturnValue([{}, {}]); const docSettings = newDocumentSettings(); const configFile = Path.resolve(Path.join(__dirname, '..', 'sampleSourceFiles', 'cSpell.json')); docSettings.registerConfigurationFile(configFile); const settings = await docSettings.getSettings({ uri: vscode_uri_1.URI.file(__filename).toString() }); expect(settings).toHaveProperty('name'); expect(settings.enabled).toBeUndefined(); expect(settings.language).toBe('en-gb'); }); it('test isExcluded', async () => { const mockFolders = [workspaceFolder]; mockGetWorkspaceFolders.mockReturnValue(mockFolders); mockGetConfiguration.mockReturnValue([{}, {}]); const docSettings = newDocumentSettings(); const configFile = Path.resolve(Path.join(__dirname, '..', 'sampleSourceFiles', 'cSpell.json')); docSettings.registerConfigurationFile(configFile); const result = await docSettings.isExcluded(vscode_uri_1.URI.file(__filename).toString()); expect(result).toBe(false); }); test('resolvePath', () => { expect(documentSettings_1.debugExports.resolvePath(__dirname)).toBe(__dirname); expect(documentSettings_1.debugExports.resolvePath('~')).toBe(os.homedir()); }); function newDocumentSettings() { return new documentSettings_1.DocumentSettings({}, {}); } }); describe('Validate RegExp corrections', () => { test('fixRegEx', () => { var _a, _b; const defaultSettings = cspell.getDefaultSettings(); // Make sure it doesn't change the defaults. expect((_a = defaultSettings.patterns) === null || _a === void 0 ? void 0 : _a.map(p => p.pattern).map(documentSettings_1.debugExports.fixRegEx)) .toEqual((_b = defaultSettings.patterns) === null || _b === void 0 ? void 0 : _b.map(p => p.pattern)); const sampleRegEx = [ '/#.*/', '/"""(.*?\\n?)+?"""/g', '/\'\'\'(.*?\\n?)+?\'\'\'/g', 'strings', ]; const expectedRegEx = [ '/#.*/', '/(""")[^\\1]*?\\1/g', "/(''')[^\\1]*?\\1/g", 'strings', ]; expect(sampleRegEx.map(documentSettings_1.debugExports.fixRegEx)).toEqual(expectedRegEx); }); test('fixPattern', () => { var _a; const defaultSettings = cspell.getDefaultSettings(); // Make sure it doesn't change the defaults. expect((_a = defaultSettings.patterns) === null || _a === void 0 ? void 0 : _a.map(documentSettings_1.debugExports.fixPattern)) .toEqual(defaultSettings.patterns); }); test('fixPattern', () => { const defaultSettings = cspell.getDefaultSettings(); // Make sure it doesn't change the defaults. expect(documentSettings_1.correctBadSettings(defaultSettings)) .toEqual(defaultSettings); const settings = { patterns: [ { name: 'strings', pattern: '/"""(.*?\\n?)+?"""/g', } ] }; const expectedSettings = { patterns: [ { name: 'strings', pattern: '/(""")[^\\1]*?\\1/g', } ] }; expect(documentSettings_1.correctBadSettings(settings)).toEqual(expectedSettings); expect(documentSettings_1.correctBadSettings(settings)).not.toEqual(settings); }); }); //# sourceMappingURL=documentSettings.test.js.map ================================================ FILE: server/index.d.ts ================================================ export * from './api'; export * from './cspellConfig'; ================================================ FILE: server/index.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); //# sourceMappingURL=index.js.map ================================================ FILE: server/logger.d.ts ================================================ export declare enum LogLevel { NONE = 0, ERROR = 1, WARNING = 2, INFO = 3, DEBUG = 4 } declare type LoggerFunction = (msg: string) => void; export interface LoggerConnection { console: { error: LoggerFunction; warn: LoggerFunction; info: LoggerFunction; log: LoggerFunction; }; onExit: (handler: () => void) => void; } export interface LogEntry { seq: number; level: LogLevel; ts: Date; msg: string; } export declare class Logger { private logLevel; private connection; private seq; private logs; private loggers; constructor(logLevel?: LogLevel, connection?: LoggerConnection); private writeLog; logMessage(level: LogLevel, msg: string): void; set level(level: LogLevel | string); get level(): LogLevel | string; setConnection(connection: LoggerConnection): void; error(msg: string): void; warn(msg: string): void; info(msg: string): void; debug(msg: string): void; log(msg: string): void; getPendingEntries(): LogEntry[]; } export {}; ================================================ FILE: server/logger.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var LogLevel; (function (LogLevel) { LogLevel[LogLevel["NONE"] = 0] = "NONE"; LogLevel[LogLevel["ERROR"] = 1] = "ERROR"; LogLevel[LogLevel["WARNING"] = 2] = "WARNING"; LogLevel[LogLevel["INFO"] = 3] = "INFO"; LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG"; })(LogLevel = exports.LogLevel || (exports.LogLevel = {})); const logLevels = [ [LogLevel[LogLevel.NONE], LogLevel.NONE], [LogLevel[LogLevel.ERROR], LogLevel.ERROR], [LogLevel[LogLevel.WARNING], LogLevel.WARNING], [LogLevel[LogLevel.INFO], LogLevel.INFO], [LogLevel[LogLevel.DEBUG], LogLevel.DEBUG], ['INFORMATION', LogLevel.INFO], ]; const levelMap = new Map(logLevels); const stub = () => { }; class Logger { constructor(logLevel = LogLevel.DEBUG, connection) { this.logLevel = logLevel; this.seq = 0; this.logs = []; this.loggers = { [LogLevel.NONE]: stub, [LogLevel.ERROR]: stub, [LogLevel.WARNING]: stub, [LogLevel.INFO]: stub, [LogLevel.DEBUG]: stub, }; if (connection) { this.setConnection(connection); } } writeLog(entry) { if (!this.connection) { this.logs.push(entry); } else { if (entry.level > this.logLevel) { return; } const message = `${entry.seq}\t${entry.ts.toISOString()}\t${entry.msg}`; const logger = this.loggers[entry.level]; if (logger) { // console.log(message); logger(message); } else { console.error(`Unknown log level: ${entry.level}; msg: ${entry.msg}`); } } } logMessage(level, msg) { const seq = ++this.seq; const entry = { seq, level, ts: new Date(), msg }; this.writeLog(entry); } set level(level) { this.logLevel = toLogLevel(level); } get level() { return this.logLevel; } setConnection(connection) { this.connection = connection; this.connection.onExit(() => { this.connection = undefined; this.loggers[LogLevel.ERROR] = stub; this.loggers[LogLevel.WARNING] = stub; this.loggers[LogLevel.INFO] = stub; this.loggers[LogLevel.DEBUG] = stub; }); this.loggers[LogLevel.ERROR] = (msg) => { connection.console.error(msg); }; this.loggers[LogLevel.WARNING] = (msg) => { connection.console.warn(msg); }; this.loggers[LogLevel.INFO] = (msg) => { connection.console.info(msg); }; this.loggers[LogLevel.DEBUG] = (msg) => { connection.console.log(msg); }; this.logs.forEach(log => this.writeLog(log)); this.logs.length = 0; } error(msg) { this.logMessage(LogLevel.ERROR, msg); } warn(msg) { this.logMessage(LogLevel.WARNING, msg); } info(msg) { this.logMessage(LogLevel.INFO, msg); } debug(msg) { this.logMessage(LogLevel.DEBUG, msg); } log(msg) { this.debug(msg); } getPendingEntries() { return this.logs; } } exports.Logger = Logger; function toLogLevel(level) { const lvl = typeof level === 'string' ? levelMap.get(level.toUpperCase()) || LogLevel.NONE : level; return typeof lvl !== 'number' || lvl < LogLevel.NONE || lvl > LogLevel.DEBUG ? LogLevel.DEBUG : lvl; } //# sourceMappingURL=logger.js.map ================================================ FILE: server/logger.test.d.ts ================================================ export {}; ================================================ FILE: server/logger.test.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("./logger"); describe('Validate Logger', () => { test('Logger Late Binding', () => { const logger = new logger_1.Logger(); logger.log('log'); logger.debug('debug'); logger.info('info'); logger.warn('warn'); logger.error('error'); // late binding const connection = makeConnection(); logger.setConnection(connection); expect(connection.exit).not.toEqual(exit); // test logging expect(connection.console.error.mock.calls.map(a => a[0])).toEqual([expect.stringContaining('error')]); expect(connection.console.warn.mock.calls.map(a => a[0])).toEqual([expect.stringContaining('warn')]); expect(connection.console.info.mock.calls.map(a => a[0])).toEqual([expect.stringContaining('info')]); expect(connection.console.log.mock.calls.map(a => a[0])).toEqual([ expect.stringContaining('log'), expect.stringContaining('debug'), ]); // test sequence expect(connection.console.error.mock.calls.map(a => a[0])).toEqual([expect.stringMatching(/^5\b/)]); expect(connection.console.warn.mock.calls.map(a => a[0])).toEqual([expect.stringMatching(/^4\b/)]); expect(connection.console.info.mock.calls.map(a => a[0])).toEqual([expect.stringMatching(/^3\b/)]); expect(connection.console.log.mock.calls.map(a => a[0])).toEqual([ expect.stringMatching(/^1\b/), expect.stringMatching(/^2\b/), ]); }); test('Logger Early Binding', () => { // late binding const connection = makeConnection(); const logger = new logger_1.Logger(logger_1.LogLevel.DEBUG, connection); logger.log('log'); logger.debug('debug'); logger.info('info'); logger.warn('warn'); logger.error('error'); expect(connection.exit).not.toEqual(exit); // test logging expect(connection.console.error.mock.calls.map(a => a[0])).toEqual([expect.stringContaining('error')]); expect(connection.console.warn.mock.calls.map(a => a[0])).toEqual([expect.stringContaining('warn')]); expect(connection.console.info.mock.calls.map(a => a[0])).toEqual([expect.stringContaining('info')]); expect(connection.console.log.mock.calls.map(a => a[0])).toEqual([ expect.stringContaining('log'), expect.stringContaining('debug'), ]); // test sequence expect(connection.console.error.mock.calls.map(a => a[0])).toEqual([expect.stringMatching(/^5\b/)]); expect(connection.console.warn.mock.calls.map(a => a[0])).toEqual([expect.stringMatching(/^4\b/)]); expect(connection.console.info.mock.calls.map(a => a[0])).toEqual([expect.stringMatching(/^3\b/)]); expect(connection.console.log.mock.calls.map(a => a[0])).toEqual([ expect.stringMatching(/^1\b/), expect.stringMatching(/^2\b/), ]); }); test('Logger onExit', () => { // late binding const connection = makeConnection(); const logger = new logger_1.Logger(logger_1.LogLevel.DEBUG, connection); expect(connection.exit).not.toEqual(exit); logger.log('log'); logger.debug('debug'); logger.info('info'); logger.warn('warn'); // exit before the last log. connection.exit(); logger.error('error'); // test logging expect(connection.console.error.mock.calls.length).toEqual(0); expect(connection.console.warn.mock.calls.length).toEqual(1); expect(connection.console.info.mock.calls.length).toEqual(1); expect(connection.console.log.mock.calls.length).toEqual(2); // re-attach logger.setConnection(connection); expect(connection.console.error.mock.calls.length).toEqual(1); expect(connection.console.warn.mock.calls.length).toEqual(1); expect(connection.console.info.mock.calls.length).toEqual(1); expect(connection.console.log.mock.calls.length).toEqual(2); }); test('Logger log level', () => { // late binding const connection = makeConnection(); const logger = new logger_1.Logger(logger_1.LogLevel.WARNING, connection); expect(connection.exit).not.toEqual(exit); logger.log('log'); logger.debug('debug'); logger.info('info'); logger.warn('warn'); logger.error('error'); // test logging expect(connection.console.error.mock.calls.length).toEqual(1); expect(connection.console.warn.mock.calls.length).toEqual(1); expect(connection.console.info.mock.calls.length).toEqual(0); expect(connection.console.log.mock.calls.length).toEqual(0); expect(logger.level).toBe(logger_1.LogLevel.WARNING); logger.level = 'error'; expect(logger.level).toBe(logger_1.LogLevel.ERROR); logger.level = 'unknown'; expect(logger.level).toBe(logger_1.LogLevel.NONE); logger.level = logger_1.LogLevel.DEBUG; logger.log('message'); expect(connection.console.log.mock.calls.length).toEqual(1); }); test('Logger get pending entries', () => { const logger = new logger_1.Logger(); logger.log('log'); logger.debug('debug'); logger.info('info'); logger.warn('warn'); logger.error('error'); const entries = logger.getPendingEntries(); expect(entries.map(e => e.msg)).toEqual([ 'log', 'debug', 'info', 'warn', 'error' ]); }); }); function exit() { } function makeConnection() { const connection = { console: { log: jest.fn(), error: jest.fn(), warn: jest.fn(), info: jest.fn(), }, onExit: (fn) => connection.exit = fn, exit, }; return connection; } //# sourceMappingURL=logger.test.js.map ================================================ FILE: server/server.d.ts ================================================ export {}; ================================================ FILE: server/server.js ================================================ "use strict"; // cSpell:ignore pycache Object.defineProperty(exports, "__esModule", { value: true }); const vscode_languageserver_1 = require("vscode-languageserver"); const vscode = require("vscode-languageserver"); const Validator = require("./validator"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const codeActions_1 = require("./codeActions"); const cspell_lib_1 = require("cspell-lib"); const CSpell = require("cspell-lib"); const cspell_lib_2 = require("cspell-lib"); const documentSettings_1 = require("./documentSettings"); const util_1 = require("./util"); util_1.log('Starting Spell Checker Server'); const notifyMethodNames = { onConfigChange: 'onConfigChange', registerConfigurationFile: 'registerConfigurationFile', }; const tds = CSpell; const defaultCheckLimit = Validator.defaultCheckLimit; // Turn off the spell checker by default. The setting files should have it set. // This prevents the spell checker from running too soon. const defaultSettings = Object.assign(Object.assign({}, CSpell.mergeSettings(cspell_lib_2.getDefaultSettings(), CSpell.getGlobalSettings())), { checkLimit: defaultCheckLimit, enabled: false }); const defaultDebounce = 50; function run() { // debounce buffer const validationRequestStream = new rxjs_1.ReplaySubject(1); const triggerUpdateConfig = new rxjs_1.ReplaySubject(1); const triggerValidateAll = new rxjs_1.ReplaySubject(1); const validationByDoc = new Map(); const blockValidation = new Map(); let isValidationBusy = false; const disposables = []; const requestMethodApi = { isSpellCheckEnabled: handleIsSpellCheckEnabled, getConfigurationForDocument: handleGetConfigurationForDocument, splitTextIntoWords: handleSplitTextIntoWords, spellingSuggestions: handleSpellingSuggestions, }; // Create a connection for the server. The connection uses Node's IPC as a transport util_1.log('Create Connection'); const connection = vscode_languageserver_1.createConnection(vscode.ProposedFeatures.all); const documentSettings = new documentSettings_1.DocumentSettings(connection, defaultSettings); // Create a simple text document manager. The text document manager // supports full document sync only const documents = new vscode_languageserver_1.TextDocuments(); connection.onInitialize((params) => { // Hook up the logger to the connection. util_1.log('onInitialize'); util_1.setWorkspaceBase(params.rootUri ? params.rootUri : ''); const capabilities = { // Tell the client that the server works in FULL text document sync mode textDocumentSync: { openClose: true, change: documents.syncKind, willSave: true, save: { includeText: true }, }, codeActionProvider: { codeActionKinds: [ vscode_languageserver_1.CodeActionKind.QuickFix ], }, }; return { capabilities }; }); // The settings have changed. Is sent on server activation as well. connection.onDidChangeConfiguration(onConfigChange); function onConfigChange(_change) { util_1.logInfo('Configuration Change'); triggerUpdateConfig.next(undefined); updateLogLevel(); } function updateActiveSettings() { util_1.log('updateActiveSettings'); documentSettings.resetSettings(); triggerValidateAll.next(undefined); } function getActiveSettings(doc) { return getActiveUriSettings(doc.uri); } function getActiveUriSettings(uri) { return documentSettings.getUriSettings(uri); } function registerConfigurationFile(path) { documentSettings.registerConfigurationFile(path); util_1.logInfo('Register Configuration File', path); triggerUpdateConfig.next(undefined); } // Listen for event messages from the client. connection.onNotification(notifyMethodNames.onConfigChange, onConfigChange); connection.onNotification(notifyMethodNames.registerConfigurationFile, registerConfigurationFile); async function handleIsSpellCheckEnabled(params) { const { uri, languageId } = params; const fileEnabled = uri ? !await isUriExcluded(uri) : undefined; const settings = await getActiveUriSettings(uri); return { languageEnabled: languageId && uri ? await isLanguageEnabled({ uri, languageId }, settings) : undefined, fileEnabled, }; } async function handleGetConfigurationForDocument(params) { const { uri, languageId } = params; const doc = uri && documents.get(uri); const docSettings = doc && (await getSettingsToUseForDocument(doc)) || undefined; const settings = await getActiveUriSettings(uri); return { languageEnabled: languageId && doc ? await isLanguageEnabled(doc, settings) : undefined, fileEnabled: uri ? !await isUriExcluded(uri) : undefined, settings, docSettings, }; } function textToWords(text) { const setOfWords = new Set(cspell_lib_1.Text.extractWordsFromCode(text) .map(t => t.text) .map(t => t.toLowerCase())); return [...setOfWords]; } function handleSplitTextIntoWords(text) { return { words: textToWords(text), }; } async function handleSpellingSuggestions(_params) { return {}; } // Register API Handlers Object.entries(requestMethodApi).forEach(([name, fn]) => { connection.onRequest(name, fn); }); // validate documents const disposableValidate = validationRequestStream .pipe(operators_1.filter(doc => !validationByDoc.has(doc.uri))) .subscribe(doc => { if (!validationByDoc.has(doc.uri)) { const uri = doc.uri; if (documentSettings_1.isUriBlackListed(uri)) { validationByDoc.set(doc.uri, validationRequestStream.pipe(operators_1.filter(doc => uri === doc.uri), operators_1.take(1), operators_1.tap(doc => util_1.log('Ignoring:', doc.uri))).subscribe()); } else { validationByDoc.set(doc.uri, validationRequestStream.pipe(operators_1.filter(doc => uri === doc.uri), operators_1.tap(doc => util_1.log(`Request Validate: v${doc.version}`, doc.uri)), operators_1.flatMap(async (doc) => ({ doc, settings: await getActiveSettings(doc) })), operators_1.debounce(dsp => rxjs_1.timer(dsp.settings.spellCheckDelayMs || defaultDebounce) .pipe(operators_1.filter(() => !isValidationBusy))), operators_1.tap(dsp => util_1.log(`blocked? ${blockValidation.has(dsp.doc.uri)}`, dsp.doc.uri)), operators_1.filter(dsp => !blockValidation.has(dsp.doc.uri)), operators_1.flatMap(validateTextDocument)).subscribe(diag => connection.sendDiagnostics(diag))); } } }); const disposableTriggerUpdateConfigStream = triggerUpdateConfig.pipe(operators_1.tap(() => util_1.log('Trigger Update Config')), operators_1.debounceTime(100)).subscribe(() => { updateActiveSettings(); }); const disposableTriggerValidateAll = triggerValidateAll .pipe(operators_1.debounceTime(250)) .subscribe(() => { util_1.log('Validate all documents'); documents.all().forEach(doc => validationRequestStream.next(doc)); }); async function shouldValidateDocument(textDocument, settings) { const { uri } = textDocument; return !!settings.enabled && isLanguageEnabled(textDocument, settings) && !await isUriExcluded(uri); } function isLanguageEnabled(textDocument, settings) { const { enabledLanguageIds = [] } = settings; return enabledLanguageIds.indexOf(textDocument.languageId) >= 0; } async function isUriExcluded(uri) { return documentSettings.isExcluded(uri); } async function getBaseSettings(doc) { const settings = await getActiveSettings(doc); return Object.assign(Object.assign({}, CSpell.mergeSettings(defaultSettings, settings)), { enabledLanguageIds: settings.enabledLanguageIds }); } async function getSettingsToUseForDocument(doc) { return tds.constructSettingsForText(await getBaseSettings(doc), doc.getText(), doc.languageId); } async function validateTextDocument(dsp) { async function validate() { const { doc, settings } = dsp; const uri = doc.uri; try { if (!documentSettings_1.isUriAllowed(uri, settings.allowedSchemas)) { const schema = uri.split(':')[0]; util_1.log(`Schema not allowed (${schema}), skipping:`, uri); return { uri, diagnostics: [] }; } const shouldCheck = await shouldValidateDocument(doc, settings); if (!shouldCheck) { util_1.log('validateTextDocument skip:', uri); return { uri, diagnostics: [] }; } const settingsToUse = await getSettingsToUseForDocument(doc); if (settingsToUse.enabled) { util_1.logInfo('Validate File', uri); util_1.log(`validateTextDocument start: v${doc.version}`, uri); const settings = documentSettings_1.correctBadSettings(settingsToUse); const diagnostics = await Validator.validateTextDocument(doc, settings); util_1.log(`validateTextDocument done: v${doc.version}`, uri); return { uri, diagnostics }; } } catch (e) { util_1.logError(`validateTextDocument: ${JSON.stringify(e)}`); } return { uri, diagnostics: [] }; } isValidationBusy = true; const r = await validate(); isValidationBusy = false; return r; } // Make the text document manager listen on the connection // for open, change and close text document events documents.listen(connection); disposables.push( // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. documents.onDidChangeContent(event => { validationRequestStream.next(event.document); }), // We want to block validation during saving. documents.onWillSave(event => { const { uri, version } = event.document; util_1.log(`onWillSave: v${version}`, uri); blockValidation.set(uri, version); }), // Enable validation once it is saved. documents.onDidSave(event => { const { uri, version } = event.document; util_1.log(`onDidSave: v${version}`, uri); blockValidation.delete(uri); validationRequestStream.next(event.document); }), // Remove subscriptions when a document closes. documents.onDidClose(event => { const uri = event.document.uri; const sub = validationByDoc.get(uri); if (sub) { validationByDoc.delete(uri); sub.unsubscribe(); } // A text document was closed we clear the diagnostics connection.sendDiagnostics({ uri, diagnostics: [] }); })); connection.onCodeAction(codeActions_1.onCodeActionHandler(documents, getBaseSettings, () => documentSettings.version, documentSettings)); // Listen on the connection connection.listen(); // Free up the validation streams on shutdown. connection.onShutdown(() => { disposables.forEach(d => d.dispose()); disposables.length = 0; disposableValidate.unsubscribe(); disposableTriggerUpdateConfigStream.unsubscribe(); disposableTriggerValidateAll.unsubscribe(); const toDispose = [...validationByDoc.values()]; validationByDoc.clear(); toDispose.forEach(sub => sub.unsubscribe()); }); function updateLogLevel() { connection.workspace.getConfiguration({ section: 'cSpell.logLevel' }).then((result) => { fetchFolders(); util_1.logger.level = result; util_1.logger.setConnection(connection); }, (reject) => { fetchFolders(); util_1.logger.level = util_1.LogLevel.DEBUG; util_1.logger.error(`Failed to get config: ${JSON.stringify(reject)}`); util_1.logger.setConnection(connection); }); } async function fetchFolders() { const folders = await connection.workspace.getWorkspaceFolders(); if (folders) { util_1.setWorkspaceFolders(folders.map(f => f.uri)); } else { util_1.setWorkspaceFolders([]); } } } run(); //# sourceMappingURL=server.js.map ================================================ FILE: server/util.d.ts ================================================ import { Logger } from './logger'; export { LogLevel } from './logger'; export declare const logger: Logger; export declare function log(msg: string, uri?: string | string[]): void; export declare function logError(msg: string, uri?: string | string[]): void; export declare function logInfo(msg: string, uri?: string | string[]): void; export declare function logDebug(msg: string, uri?: string | string[]): void; export declare function setWorkspaceBase(uri: string): void; export declare function setWorkspaceFolders(folders: string[]): void; export declare function uniqueFilter(): (v: T) => boolean; export declare function uniqueFilter(extractFn: (v: T) => U): (v: T) => boolean; ================================================ FILE: server/util.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const logger_1 = require("./logger"); var logger_2 = require("./logger"); exports.LogLevel = logger_2.LogLevel; let workspaceBase = ''; let workspaceFolders = []; exports.logger = new logger_1.Logger(); function log(msg, uri) { exports.logger.log(formatMessage(msg, uri)); } exports.log = log; function logError(msg, uri) { exports.logger.error(formatMessage(msg, uri)); } exports.logError = logError; function logInfo(msg, uri) { exports.logger.info(formatMessage(msg, uri)); } exports.logInfo = logInfo; function logDebug(msg, uri) { exports.logger.debug(formatMessage(msg, uri)); } exports.logDebug = logDebug; function setWorkspaceBase(uri) { log(`setWorkspaceBase URI: ${uri}`); workspaceBase = uri; } exports.setWorkspaceBase = setWorkspaceBase; function setWorkspaceFolders(folders) { log(`setWorkspaceFolders folders URI: [${folders.join('\n')}]`); workspaceFolders = folders; setWorkspaceBase(findCommonBasis(workspaceFolders)); } exports.setWorkspaceFolders = setWorkspaceFolders; function formatMessage(msg, uri) { const uris = Array.isArray(uri) ? uri : [uri]; return msg + '\t' + uris.map(normalizeUri).join('\n\t\t\t'); } function normalizeUri(uri) { if (!uri) { return ''; } const base = findCommonBase(uri, workspaceBase); return base ? uri.replace(base, '...') : uri; } function findCommonBasis(folders) { return folders.reduce((a, b) => findCommonBase(a || b, b), ''); } function findCommonBase(a, b) { const limit = matchingUriLength(a, b); return a.slice(0, limit); } function matchingUriLength(a, b) { const sep = '/'; const aParts = a.split(sep); const bParts = b.split(sep); const limit = Math.min(aParts.length, bParts.length); let i = 0; for (i = 0; i < limit && aParts[i] === bParts[i]; i += 1) { } return aParts.slice(0, i).join(sep).length; } function uniqueFilter(extractFn) { const values = new Set(); const extractor = extractFn || (a => a); return (v) => { const vv = extractor(v); const ret = !values.has(vv); values.add(vv); return ret; }; } exports.uniqueFilter = uniqueFilter; //# sourceMappingURL=util.js.map ================================================ FILE: server/util.test.d.ts ================================================ export {}; ================================================ FILE: server/util.test.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const util_1 = require("./util"); describe('Validate Util Functions', () => { test('Logging', () => { util_1.setWorkspaceFolders([__dirname, __dirname]); util_1.log('log', __filename); util_1.logError('error'); util_1.logDebug('debug'); util_1.logInfo('info'); expect(util_1.logger.getPendingEntries().map(e => e.msg)).toEqual([ expect.stringContaining('setWorkspaceFolders'), expect.stringContaining('setWorkspaceBase'), expect.stringMatching(/log\s+.*util.test.ts/), expect.stringContaining('error'), expect.stringContaining('debug'), expect.stringContaining('info'), ]); }); test('Unique filter', () => { expect([].filter(util_1.uniqueFilter())).toEqual([]); expect([1, 2, 3].filter(util_1.uniqueFilter())).toEqual([1, 2, 3]); expect([1, 2, 3, 3, 2, 1].filter(util_1.uniqueFilter())).toEqual([1, 2, 3]); const a = { id: 'a', v: 1 }; const b = { id: 'b', v: 1 }; const aa = { id: 'a', v: 2 }; expect([a, a, b, aa, b].filter(util_1.uniqueFilter())).toEqual([a, b, aa]); expect([a, a, b, aa, b, aa].filter(util_1.uniqueFilter(a => a.id))).toEqual([a, b]); }); }); //# sourceMappingURL=util.test.js.map ================================================ FILE: server/validator.d.ts ================================================ import { TextDocument, Diagnostic } from 'vscode-languageserver'; import { CSpellUserSettings } from './cspellConfig'; import { Sequence } from 'gensequence'; export { validateText } from 'cspell-lib'; export declare const diagnosticCollectionName = "cSpell"; export declare const diagSource = "cSpell"; export declare const defaultCheckLimit = 500; export declare function validateTextDocument(textDocument: TextDocument, options: CSpellUserSettings): Promise; export declare function validateTextDocumentAsync(textDocument: TextDocument, options: CSpellUserSettings): Promise>; ================================================ FILE: server/validator.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vscode_languageserver_1 = require("vscode-languageserver"); const cspell_lib_1 = require("cspell-lib"); const gensequence_1 = require("gensequence"); var cspell_lib_2 = require("cspell-lib"); exports.validateText = cspell_lib_2.validateText; exports.diagnosticCollectionName = 'cSpell'; exports.diagSource = exports.diagnosticCollectionName; exports.defaultCheckLimit = 500; const diagSeverityMap = new Map([ ['error', vscode_languageserver_1.DiagnosticSeverity.Error], ['warning', vscode_languageserver_1.DiagnosticSeverity.Warning], ['information', vscode_languageserver_1.DiagnosticSeverity.Information], ['hint', vscode_languageserver_1.DiagnosticSeverity.Hint], ]); async function validateTextDocument(textDocument, options) { return (await validateTextDocumentAsync(textDocument, options)).toArray(); } exports.validateTextDocument = validateTextDocument; async function validateTextDocumentAsync(textDocument, options) { const { diagnosticLevel = vscode_languageserver_1.DiagnosticSeverity.Information.toString() } = options; const severity = diagSeverityMap.get(diagnosticLevel.toLowerCase()) || vscode_languageserver_1.DiagnosticSeverity.Information; const limit = (options.checkLimit || exports.defaultCheckLimit) * 1024; const text = textDocument.getText().slice(0, limit); const diags = gensequence_1.genSequence(await cspell_lib_1.validateText(text, options)) // Convert the offset into a position .map(offsetWord => (Object.assign(Object.assign({}, offsetWord), { position: textDocument.positionAt(offsetWord.offset) }))) // Calculate the range .map(word => (Object.assign(Object.assign({}, word), { range: { start: word.position, end: (Object.assign(Object.assign({}, word.position), { character: word.position.character + word.text.length })) } }))) // Convert it to a Diagnostic .map(({ text, range }) => ({ severity, range: range, message: `"${text}": Unknown word.`, source: exports.diagSource })); return diags; } exports.validateTextDocumentAsync = validateTextDocumentAsync; //# sourceMappingURL=validator.js.map ================================================ FILE: server/validator.test.d.ts ================================================ export {}; ================================================ FILE: server/validator.test.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Validator = require("./validator"); const loremIpsum = require("lorem-ipsum"); const cspell = require("cspell-lib"); const vscode_uri_1 = require("vscode-uri"); const cspell_lib_1 = require("cspell-lib"); const vscode_languageserver_1 = require("vscode-languageserver"); // cSpell:ignore brouwn jumpped lazzy wrongg mispelled ctrip nmove mischecked const defaultSettings = Object.assign(Object.assign({}, cspell_lib_1.getDefaultSettings()), { enabledLanguageIds: ['plaintext', 'javascript'] }); function getSettings(text, languageId) { return cspell.constructSettingsForText(defaultSettings, text, languageId); } describe('Validator', () => { test('validates the validator', () => { const text = 'The quick brouwn fox jumpped over the lazzy dog.'; const languageId = 'plaintext'; const settings = getSettings(text, languageId); const results = Validator.validateText(text, settings); return results.then(results => { const words = results.map(({ text }) => text); expect(words).toEqual(['brouwn', 'jumpped', 'lazzy']); }); }); test('validates ignore Case', () => { const text = 'The Quick brown fox Jumped over the lazy dog.'; const languageId = 'plaintext'; const settings = getSettings(text, languageId); const results = Validator.validateText(text, settings); return results.then(results => { const words = results.map(({ text }) => text); expect(words).toEqual([]); }); }); test('validate limit', () => { const text = loremIpsum({ count: 5, units: 'paragraphs' }); const languageId = 'plaintext'; const settings = Object.assign(Object.assign({}, getSettings(text, languageId)), { maxNumberOfProblems: 10 }); const results = Validator.validateText(text, settings); return results.then(results => expect(results).toHaveLength(10)); }); test('validates reserved words', () => { const text = 'constructor const prototype type typeof null undefined'; const languageId = 'javascript'; const settings = Object.assign(Object.assign({}, getSettings(text, languageId)), { maxNumberOfProblems: 10 }); const results = Validator.validateText(text, settings); return results.then(results => expect(results).toHaveLength(0)); }); test('validates regex inclusions/exclusions', async () => { const text = sampleCode; const languageId = 'plaintext'; const settings = Object.assign(Object.assign({}, getSettings(text, languageId)), { maxNumberOfProblems: 10 }); const results = await Validator.validateText(text, settings); const words = results.map(wo => wo.text); expect(words).toEqual(expect.arrayContaining(['wrongg'])); expect(words).toEqual(expect.arrayContaining(['mispelled'])); expect(words).toEqual(expect.not.arrayContaining(['xaccd'])); expect(words).toEqual(expect.not.arrayContaining(['ctrip'])); expect(words).toEqual(expect.not.arrayContaining(['FFEE'])); expect(words).toEqual(expect.not.arrayContaining(['nmove'])); }); test('validates ignoreRegExpList', () => { const text = sampleCode; const languageId = 'plaintext'; const settings = Object.assign(Object.assign({}, getSettings(text, languageId)), { maxNumberOfProblems: 10, ignoreRegExpList: ['^const [wy]RON[g]+', 'mis.*led'] }); const results = Validator.validateText(text, settings); return results.then(results => { const words = results.map(wo => wo.text); expect(words).toEqual(expect.not.arrayContaining(['wrongg'])); expect(words).toEqual(expect.not.arrayContaining(['mispelled'])); expect(words).toEqual(expect.arrayContaining(['mischecked'])); }); }); test('validates ignoreRegExpList 2', () => { const results = Validator.validateText(sampleCode, { ignoreRegExpList: ['/^const [wy]ron[g]+/gim', '/MIS...LED/g', '/mischecked'] }); return results.then(results => { const words = results.map(wo => wo.text); expect(words).toEqual(expect.not.arrayContaining(['wrongg'])); expect(words).toEqual(expect.arrayContaining(['mispelled'])); expect(words).toEqual(expect.arrayContaining(['mischecked'])); }); }); test('validates malformed ignoreRegExpList', () => { const results = Validator.validateText(sampleCode, { ignoreRegExpList: ['/wrong[/gim', 'mis.*led'] }); return results.then(results => { const words = results.map(wo => wo.text); expect(words).toEqual(expect.arrayContaining(['wrongg'])); expect(words).toEqual(expect.not.arrayContaining(['mispelled'])); expect(words).toEqual(expect.arrayContaining(['mischecked'])); }); }); test('validateTextDocument', async () => { const text = sampleCode; const languageId = 'plaintext'; const settings = Object.assign(Object.assign({}, getSettings(text, languageId)), { maxNumberOfProblems: 10 }); const uri = vscode_uri_1.URI.file(__filename).toString(); const textDoc = vscode_languageserver_1.TextDocument.create(uri, languageId, 1, text); const results = await Validator.validateTextDocument(textDoc, settings); const words = results.map(diag => diag.message); expect(words).toEqual(expect.arrayContaining([expect.stringContaining('wrongg')])); expect(words).toEqual(expect.arrayContaining([expect.stringContaining('mispelled')])); expect(words).toEqual(expect.not.arrayContaining([expect.stringContaining('xaccd')])); expect(words).toEqual(expect.not.arrayContaining([expect.stringContaining('ctrip')])); expect(words).toEqual(expect.not.arrayContaining([expect.stringContaining('FFEE')])); expect(words).toEqual(expect.not.arrayContaining([expect.stringContaining('nmove')])); }); }); const sampleCode = ` // Verify urls do not get checked. const url = 'http://ctrip.com?q=words'; // Verify hex values. const value = 0xaccd; /* spell-checker:disable */ const weirdWords = ['ctrip', 'xebia', 'zando', 'zooloo']; /* spell-checker:enable */ const wrongg = 'mispelled'; const check = 'mischecked'; const message = "\\nmove to next line"; const hex = 0xBADC0FFEE; `; //# sourceMappingURL=validator.test.js.map ================================================ FILE: server/vscode.config.d.ts ================================================ import * as vscode from 'vscode-languageserver'; export interface TextDocumentUri { uri: string; } export interface TextDocumentUriLangId extends TextDocumentUri { languageId: string; } export declare type Connection = vscode.Connection; export declare type GetConfigurationParams = string | vscode.ConfigurationItem | vscode.ConfigurationItem[]; export declare function getConfiguration(connection: Connection): Thenable; export declare function getConfiguration(connection: Connection, section: string): Thenable; export declare function getConfiguration(connection: Connection, item: vscode.ConfigurationItem): Thenable; export declare function getConfiguration(connection: Connection, items: vscode.ConfigurationItem[]): Thenable; /** * Just a pass through function to `connection.workspace.getWorkspaceFolders` * Useful for mocking. * @param connection */ export declare function getWorkspaceFolders(connection: Connection): Thenable; ================================================ FILE: server/vscode.config.js ================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const util_1 = require("./util"); function getConfiguration(connection, params) { if (typeof params === 'string') { util_1.log(`getConfiguration\t${params}`); return connection.workspace.getConfiguration(params); } if (Array.isArray(params)) { const uris = params .map(p => { if (!p) { return ''; } if (typeof p === 'string') { return p; } return p.scopeUri || ''; }) .filter(p => !!p); util_1.log('getConfiguration', uris); return connection.workspace.getConfiguration(params); } if (params) { util_1.log('getConfiguration', params.scopeUri); return connection.workspace.getConfiguration(params); } return connection.workspace.getConfiguration(); } exports.getConfiguration = getConfiguration; /** * Just a pass through function to `connection.workspace.getWorkspaceFolders` * Useful for mocking. * @param connection */ function getWorkspaceFolders(connection) { return connection.workspace.getWorkspaceFolders(); } exports.getWorkspaceFolders = getWorkspaceFolders; //# sourceMappingURL=vscode.config.js.map ================================================ FILE: src/client/client.ts ================================================ import * as coc from 'coc.nvim'; import { TextDocument } from 'vscode-languageserver-protocol'; import { GetConfigurationForDocumentResult, NotifyServerMethods, ServerRequestMethodResults, ServerRequestMethodConstants, SplitTextIntoWordsResult, ServerRequestMethodRequests, } from '../server'; import * as Settings from '../settings'; import * as LanguageIds from '../settings/languageIds'; import { Maybe, supportedSchemes, setOfSupportedSchemes } from '../util'; // The debug options for the server const debugExecArgv = ['--nolazy', '--inspect=60048']; const diagnosticCollectionName = 'cSpell'; export interface ServerResponseIsSpellCheckEnabled { languageEnabled?: boolean; fileEnabled?: boolean; } const methodNames: ServerRequestMethodConstants = { isSpellCheckEnabled: 'isSpellCheckEnabled', getConfigurationForDocument: 'getConfigurationForDocument', splitTextIntoWords: 'splitTextIntoWords', spellingSuggestions: 'spellingSuggestions', }; const defaultGetConfigurationForDocumentResult: GetConfigurationForDocumentResult = { languageEnabled: undefined, fileEnabled: undefined, settings: undefined, docSettings: undefined, }; export class CSpellClient { readonly client: coc.LanguageClient; readonly import: Set = new Set(); readonly languageIds: Set; readonly allowedSchemas: Set; /** * @param: {string} module -- absolute path to the server module. */ constructor(module: string, languageIds: string[]) { const enabledLanguageIds = Settings.getScopedSettingFromVSConfig('enabledLanguageIds', Settings.Scopes.Workspace); this.allowedSchemas = new Set( Settings.getScopedSettingFromVSConfig('allowedSchemas', Settings.Scopes.Workspace) || supportedSchemes ); setOfSupportedSchemes.clear(); this.allowedSchemas.forEach(schema => setOfSupportedSchemes.add(schema)); this.languageIds = new Set( languageIds .concat(enabledLanguageIds || []) .concat(LanguageIds.languageIds) ); const uniqueLangIds = [...this.languageIds]; const documentSelector = [...this.allowedSchemas] .map(scheme => uniqueLangIds.map(language => ({ language, scheme }))) .reduce( (a, b) => a.concat(b)); // Options to control the language client const clientOptions: coc.LanguageClientOptions = { documentSelector, diagnosticCollectionName, synchronize: { // Synchronize the setting section 'spellChecker' to the server configurationSection: ['cSpell', 'search'] } }; const execArgv = this.calcServerArgs(); const options: coc.ForkOptions = { execArgv }; // The debug options for the server const debugOptions: coc.ForkOptions = { execArgv: [...execArgv, ...debugExecArgv] }; // If the extension is launched in debug mode the debug server options are use // Otherwise the run options are used const serverOptions: coc.ServerOptions = { run : { module, transport: coc.TransportKind.ipc, options }, debug: { module, transport: coc.TransportKind.ipc, options: debugOptions } }; // Create the language client and start the client. this.client = new coc.LanguageClient('cSpell', 'Code Spell Checker', serverOptions, clientOptions); this.client.registerProposedFeatures(); } public needsStart() { return this.client.needsStart(); } public needsStop() { return this.client.needsStop(); } public start() { return this.client.start(); } public async isSpellCheckEnabled(document: TextDocument): Promise { const { uri, languageId = '' } = document; if (!uri || !languageId) { return {}; } const response = (await this.sendRequest( methodNames.isSpellCheckEnabled, { uri: uri.toString(), languageId } )) as ServerResponseIsSpellCheckEnabled; return response; } public async getConfigurationForDocument(document: TextDocument | undefined): Promise { if (!document) { return (await this.sendRequest(methodNames.getConfigurationForDocument, {})); } const { uri, languageId = '' } = document; if (!uri || !languageId) { return (await this.sendRequest(methodNames.getConfigurationForDocument, {})); } const result = await this.sendRequest( methodNames.getConfigurationForDocument, { uri: uri.toString(), languageId } ); return result; } public splitTextIntoDictionaryWords(text: string): Thenable { return this.sendRequest( methodNames.splitTextIntoWords, text ); } public async fetchSpellingSuggestions(text: string, languageId: string | undefined, document: TextDocument | undefined) { await this.client.onReady(); return; // this.sendRequest(); } public notifySettingsChanged() { return this.client.onReady().then(() => this.sendNotification('onConfigChange')); } public registerConfiguration(path: string) { return this.client.onReady().then(() => this.sendNotification('registerConfigurationFile', path)); } get diagnostics(): Maybe { return (this.client && this.client.diagnostics) || undefined; } public triggerSettingsRefresh() { return this.notifySettingsChanged(); } private async sendRequest( method: K, param: ServerRequestMethodRequests[K] ): Promise { await this.client.onReady(); return this.client.sendRequest(method, param); } private sendNotification(method: NotifyServerMethods, params?: any): void { this.client.sendNotification(method, params); } public static create(module: string) { return coc.workspace.nvim.eval(`sort(map(split(globpath(&rtp, 'syntax/*.vim'), '\n'),'fnamemodify(v:val, ":t:r")'))`) .then(langIds => new CSpellClient(module, langIds as string[])); } public isLookBackSupported(): boolean { try { return /(?<=\s)x/.test(' x'); } catch (_) {} return false; } private calcServerArgs(): string[] { const args: string[] = []; if (!this.isLookBackSupported()) { args.push('--harmony_regexp_lookbehind'); } return args; } } ================================================ FILE: src/client/index.ts ================================================ export * from './client'; ================================================ FILE: src/commands.ts ================================================ import * as coc from 'coc.nvim'; import { TextEdit, TextDocument, Range } from 'vscode-languageserver-protocol'; import * as CSpellSettings from './settings/CSpellSettings'; import * as Settings from './settings'; export { toggleEnableSpellChecker, enableCurrentLanguage, disableCurrentLanguage } from './settings'; const rangeContain = (origin: Range, target: Range): boolean => { return origin.start.line <= target.start.line && origin.start.character <= target.start.character && origin.end.line >= target.end.line && origin.end.character >= target.end.character; }; export function handlerApplyTextEdits(_client: coc.LanguageClient) { return async function applyTextEdits(uri: string, documentVersion: number, edits: TextEdit[]) { const doc = await coc.workspace.document; const activeDoc = doc && doc.textDocument; if (activeDoc && activeDoc.uri === uri) { if (activeDoc.version !== documentVersion) { return coc.workspace.showMessage(`Spelling changes are outdated and cannot be applied to the document.`); } const cfg = coc.workspace.getConfiguration(CSpellSettings.sectionCSpell); if (cfg.get('fixSpellingWithRenameProvider') && edits.length === 1) { const edit = edits[0]; if (await attemptRename(activeDoc, edit.range, edit.newText)) { return; } } coc.workspace.applyEdit({ changes: { [uri]: edits } }); } }; } async function attemptRename(document: TextDocument, range: Range, text: string): Promise { if (!coc.languages.hasProvider('rename', document)) { return false; } if (range.start.line !== range.end.line) { return false; } const doc = await coc.workspace.getDocument(document.uri); const wordRange = doc.getWordRangeAtPosition(range.start); if (!wordRange || !rangeContain(wordRange, range)) { return false; } try { const prepareRename = await coc.languages.prepareRename(document, range.start); if (prepareRename === false) { return false; } } catch (error) { return false; } const orig = wordRange.start.character; const a = range.start.character - orig; const b = range.end.character - orig; const docText = document.getText(wordRange); const newText = [docText.slice(0, a), text, docText.slice(b)].join(''); const workspaceEdit = await coc.languages.provideRenameEdits(document, range.start, newText); return workspaceEdit && await coc.workspace.applyEdit(workspaceEdit); } export function addWordToWorkspaceDictionary(word: string, uri: string | null | coc.Uri | undefined): Thenable { return addWordToTarget(word, Settings.Target.Workspace, uri); } export function addWordToUserDictionary(word: string): Thenable { return addWordToTarget(word, Settings.Target.Global, undefined); } async function addWordToTarget(word: string, target: Settings.Target, uri: string | null | coc.Uri | undefined) { const actualTarget = resolveTarget(target, uri); await Settings.addWordToSettings(actualTarget, word); const path = await determineSettingsPath(actualTarget, uri); if (path) { await CSpellSettings.addWordToSettingsAndUpdate(path, word); } } export async function addIgnoreWordToTarget(word: string, target: Settings.Target, uri: string | null | coc.Uri | undefined) { const actualTarget = resolveTarget(target, uri); await Settings.addIgnoreWordToSettings(actualTarget, word); const path = await determineSettingsPath(actualTarget, uri); if (path) { await CSpellSettings.addIgnoreWordToSettingsAndUpdate(path, word); } } export function removeWordFromWorkspaceDictionary(word: string, uri: string | null | coc.Uri | undefined): Thenable { return removeWordFromTarget(word, Settings.Target.Workspace, uri); } export function removeWordFromUserDictionary(word: string): Thenable { return removeWordFromTarget(word, Settings.Target.Global, undefined); } async function removeWordFromTarget(word: string, target: Settings.Target, uri: string | null | coc.Uri | undefined) { const actualTarget = resolveTarget(target, uri); await Settings.removeWordFromSettings(actualTarget, word); const path = await determineSettingsPath(actualTarget, uri); if (path) { await CSpellSettings.removeWordFromSettingsAndUpdate(path, word); } } async function determineSettingsPath( target: Settings.ConfigTarget, uri: string | null | coc.Uri | undefined ): Promise { if (target !== Settings.Target.Global) { const useUri = uri ? pathToUri(uri) : undefined; return Settings.findExistingSettingsFileLocation(useUri); } return undefined; } function resolveTarget(target: Settings.Target, uri?: string | null | coc.Uri): Settings.ConfigTarget { if (target === Settings.Target.Global || !Settings.hasWorkspaceLocation()) { return Settings.Target.Global; } if (!uri) { return Settings.Target.Workspace; } const resolvedUri = pathToUri(uri); return Settings.createTargetForUri(target, resolvedUri); } export async function enableLanguageId(languageId: string): Promise { if (!languageId) { languageId = await coc.workspace.requestInput('Input enable language Id', ''); if (!languageId) { return; } } const doc = await coc.workspace.document; const uri = doc && doc.textDocument.uri; return Settings.enableLanguageIdForClosestTarget(languageId, true, uri ? coc.Uri.parse(uri) : undefined); } export async function disableLanguageId(languageId: string): Promise { if (!languageId) { languageId = await coc.workspace.requestInput('Input disable language Id', ''); if (!languageId) { return; } } const doc = await coc.workspace.document; const uri = doc && doc.textDocument.uri; return Settings.enableLanguageIdForClosestTarget(languageId, false, uri ? coc.Uri.parse(uri) : undefined); } export function userCommandOnCurrentSelectionOrPrompt( prompt: string, fnAction: (text: string, uri: coc.Uri | undefined) => Thenable ): () => Thenable { return async function () { const document = await coc.workspace.document; const mode = await coc.workspace.nvim.call('visualmode') as string; let range = mode ? await coc.workspace.getSelectedRange(mode, document) : null; let value = range ? document.textDocument.getText(range) : ''; if (range && !(range.start.line !== range.end.line || range.start.character !== range.end.character)) { fnAction(value, coc.Uri.parse(document.textDocument.uri)); } else { const position = await coc.workspace.getCursorPosition(); if (position && document && document.textDocument) { range = document.getWordRangeAtPosition(position); value = range ? document.textDocument.getText(range) : ''; } const word = await coc.workspace.requestInput(prompt, value); word && fnAction(word, coc.Uri.parse(document.textDocument.uri)); } }; } function pathToUri(uri: string | coc.Uri): coc.Uri { if (uri instanceof coc.Uri) { return uri; } return coc.Uri.parse(uri); } ================================================ FILE: src/dict.ts ================================================ import * as vim from 'cspell-dict-vimlang'; import {ExtensionApi} from './extensionApi'; // enable default dict const dicts = [ vim ]; export function activateDict(api: ExtensionApi) { dicts.forEach(dict => { const path = dict.getConfigLocation(); // We need to register the dictionary configuration with the Code Spell Checker Extension api && api.registerConfig && api.registerConfig(path); }); } ================================================ FILE: src/extensionApi.ts ================================================ import * as coc from 'coc.nvim'; import {CSpellClient} from './client'; import {ConfigTarget} from './settings'; export interface ExtensionApi { registerConfig(path: string): void; triggerGetSettings(): void; enableLanguageId(languageId: string, uri?: string): Thenable; disableLanguageId(languageId: string, uri?: string): Thenable; enableCurrentLanguage(): Thenable; disableCurrentLanguage(): Thenable; addWordToUserDictionary(word: string): Thenable; addWordToWorkspaceDictionary(word: string, uri?: string | null | coc.Uri): Thenable; enableLocal(target: ConfigTarget, local: string): Thenable; disableLocal(target: ConfigTarget, local: string): Thenable; updateSettings(): boolean; cSpellClient(): CSpellClient; } ================================================ FILE: src/fileTypes/fileTypeMap.json ================================================ { "fileTypeNames": [ "1C (BSL).bsl", "1C (MDO).mdo", "Adobe Illustrator.ai", "Adobe Photoshop.psd", "ApexComponent.component", "archive.zip", "Assembly.asm", "Assembly.s", "Audio File.wav", "Batch.bat", "Bower.json", "C Sharp.cs", "C.c", "C++.cpp", "Cake Build.cake", "Cake PHP.ctp", "cert.cert", "Closure.clj", "Cmd.cmd", "CoffeScript.coffee", "ColdFusion.cfm", "crystal_embedded.ecr", "crystal_embedded.slang", "crystal.cr", "CSS.css", "CSV.csv", "D.d", "docker-compose.yml", "docker-healthcheck", "Dockerfile", "EcmaScript.es6", "EJS.ejs", "elixir_script.exs", "elixir.ex", "Elm.elm", "Excell.xlsx", "F Sharp Script.fsx", "F Sharp.fs", "Favicon.ico", "firebase.json", "geckodriver", "Go.go", "Gradle.gradle", "Grails.groovy", "Gruntfile.js", "gulpfile.babel.js", "Gulpfile.js", "Hacklang.hh", "Haml.haml", "Handlebars.hbs", "Haskell.hs", "Haxe make.hxml", "Haxe module.hx", "Haxe project.hxp", "Haxe script.hxs", "HTML.erb", "HTML.html", "Image.ai", "ionic.config.json", "ionic.project", "Jade.jade", "Jar.jar", "Java.java", "JavaScript.js", "JavaScript.spec.js", "Jenkinsfile", "Jinja.jinja2", "JSON.json", "Julia.jl", "karma.conf.js", "keyfile.key", "Kotlin Script.kts", "Kotlin.kt", "Latex.tex", "Less.less", "LICENSE", "LiterateHaskell.lhs", "LiveScript.ls", "Lua.lua", "Makefile", "Markdown.md", "mime.types", "Mustache.mustache", "mvnw", "nginx.conf", "Nunjucks.njk", "Objective C.h", "Objective C.m", "oCaml.ml", "Odata.odata", "PDF.pdf", "Perl.pl", "PHP.php.inc", "PHP.php", "Powershell.manifest.psd1", "Powershell.module.psm1", "Powershell.ps1", "Procfile", "Pug.pug", "Puppet.pp", "Python.py", "R.R", "Raml.raml", "React Coffee.cjsx", "React Typescript.tsx", "React.jsx", "rollup.config.js", "Ruby.rb", "Rust.rs", "Sass.scss", "Sbt.sbt", "Scala.scala", "Settings.config", "Shell.sh", "Shopify.liquid", "Slim.slim", "Smarty.smarty.tpl", "Solidity.sol", "Spring.springBeans", "SQL.sql", "Stache.stache", "Stylus.styl", "Sublime.sublime-project", "Sugarss.sss", "SVGX.svgx", "Swift.swift", "Temp.tmp", "Terraform.tf.json", "Terraform.tf", "Text.txt", "TODO", "TOML.toml", "Twig.twig", "Typescript.ts", "Vala.vala", "Video File.mov", "Vue.vue", "WebAssembly Text.wat", "WebAssembly.wasm", "webpack.config.js", "Widget.wgt", "Word.docx", "XML.xml", "yarn.lock", "YML.yml" ] } ================================================ FILE: src/index.ts ================================================ import * as coc from 'coc.nvim'; import * as path from 'path'; import * as settings from './settings'; import {CSpellClient} from './client'; import { initStatusBar } from './statusbar'; import {userCommandOnCurrentSelectionOrPrompt, handlerApplyTextEdits} from './commands'; import * as commands from './commands'; import { ExtensionApi } from './extensionApi'; import {activateDict} from './dict'; export async function activate(context: coc.ExtensionContext): Promise { // The server is implemented in node const serverModule = context.asAbsolutePath(path.join('server', 'server.js')); // Get the cSpell Client const client = await CSpellClient.create(serverModule); // Start the client. const clientDispose = client.start(); function triggerGetSettings() { client.triggerSettingsRefresh(); } function registerConfig(path: string) { client.registerConfiguration(path); } function splitTextFn( apply: (word: string, uri: string | coc.Uri | null | undefined) => Thenable ): (word: string, uri: string | coc.Uri | null | undefined) => Thenable { return async (word: string, uri: string | coc.Uri | null | undefined) => { const doc = await coc.workspace.document; const document = doc && doc.textDocument; const uriToUse = uri || document && document.uri || null; return client.splitTextIntoDictionaryWords(word) .then(result => result.words) .then(words => apply(words.join(' '), uriToUse)); }; } const actionAddWordToWorkspace = userCommandOnCurrentSelectionOrPrompt( 'Add Word to Workspace Dictionary', splitTextFn(commands.addWordToWorkspaceDictionary) ); const actionAddWordToDictionary = userCommandOnCurrentSelectionOrPrompt( 'Add Word to User Dictionary', splitTextFn(commands.addWordToUserDictionary) ); const actionAddIgnoreWord = userCommandOnCurrentSelectionOrPrompt( 'Ignore Word', splitTextFn((word, uri) => commands.addIgnoreWordToTarget(word, settings.Target.Workspace, uri)) ); const actionAddIgnoreWordToWorkspace = userCommandOnCurrentSelectionOrPrompt( 'Ignore Word in Workspace Settings', splitTextFn((word, uri) => commands.addIgnoreWordToTarget(word, settings.Target.Workspace, uri)) ); const actionAddIgnoreWordToUser = userCommandOnCurrentSelectionOrPrompt( 'Ignore Word in User Settings', splitTextFn((word, uri) => commands.addIgnoreWordToTarget(word, settings.Target.Global, uri)) ); const actionRemoveWordFromWorkspaceDictionary = userCommandOnCurrentSelectionOrPrompt( 'Remove Word from Dictionary', splitTextFn(commands.removeWordFromWorkspaceDictionary) ); const actionRemoveWordFromDictionary = userCommandOnCurrentSelectionOrPrompt( 'Remove Word from Dictionary', splitTextFn(commands.removeWordFromUserDictionary) ); initStatusBar(context, client); // Push the disposable to the context's subscriptions so that the // client can be deactivated on extension deactivation context.subscriptions.push( clientDispose, // internal commands that server uses coc.commands.registerCommand('cSpell.editText', handlerApplyTextEdits(client.client), null, true), coc.commands.registerCommand('cSpell.addWordToDictionarySilent', commands.addWordToWorkspaceDictionary, null, true), coc.commands.registerCommand('cSpell.addWordToWorkspaceDictionarySilent', commands.addWordToWorkspaceDictionary, null, true), coc.commands.registerCommand('cSpell.addWordToUserDictionarySilent', commands.addWordToUserDictionary, null, true), coc.commands.registerCommand('cSpell.addWordToDictionary', actionAddWordToWorkspace), coc.commands.registerCommand('cSpell.addWordToWorkspaceDictionary', actionAddWordToWorkspace), coc.commands.registerCommand('cSpell.addWordToUserDictionary', actionAddWordToDictionary), coc.commands.registerCommand('cSpell.addIgnoreWord', actionAddIgnoreWord), coc.commands.registerCommand('cSpell.addIgnoreWordToFolder', actionAddIgnoreWordToWorkspace), coc.commands.registerCommand('cSpell.addIgnoreWordToWorkspace', actionAddIgnoreWordToWorkspace), coc.commands.registerCommand('cSpell.addIgnoreWordToUser', actionAddIgnoreWordToUser), coc.commands.registerCommand('cSpell.removeWordFromFolderDictionary', actionRemoveWordFromWorkspaceDictionary), coc.commands.registerCommand('cSpell.removeWordFromWorkspaceDictionary', actionRemoveWordFromWorkspaceDictionary), coc.commands.registerCommand('cSpell.removeWordFromUserDictionary', actionRemoveWordFromDictionary), coc.commands.registerCommand('cSpell.enableLanguage', commands.enableLanguageId), coc.commands.registerCommand('cSpell.disableLanguage', commands.disableLanguageId), coc.commands.registerCommand('cSpell.enableForWorkspace', () => settings.setEnableSpellChecking(settings.Target.Workspace, false)), coc.commands.registerCommand('cSpell.disableForWorkspace', () => settings.setEnableSpellChecking(settings.Target.Workspace, false)), coc.commands.registerCommand('cSpell.toggleEnableSpellChecker', commands.toggleEnableSpellChecker), coc.commands.registerCommand('cSpell.enableCurrentLanguage', commands.enableCurrentLanguage), coc.commands.registerCommand('cSpell.disableCurrentLanguage', commands.disableCurrentLanguage), settings.watchSettingsFiles(triggerGetSettings), ); const server = { registerConfig, triggerGetSettings, enableLanguageId: commands.enableLanguageId, disableLanguageId: commands.disableLanguageId, enableCurrentLanguage: commands.enableCurrentLanguage, disableCurrentLanguage: commands.disableCurrentLanguage, addWordToUserDictionary: commands.addWordToUserDictionary, addWordToWorkspaceDictionary: commands.addWordToWorkspaceDictionary, enableLocal: settings.enableLocal, disableLocal: settings.disableLocal, updateSettings: () => false, cSpellClient: () => client, }; // activate default dicts activateDict(server); return server; } ================================================ FILE: src/iso639-1/index.ts ================================================ import { codes } from './languageCodes'; export interface LangCountryPair { lang: string; country: string; } const langCodes = new Map( codes .map((parts) => { const [code, lang, country = ''] = parts; return [code, { lang, country }] as [string, LangCountryPair]; }) ); const regExReplace = /^([a-z]{2})[-_]?([a-z]{0,2})$/i; // const regExValidate = /^([a-z]{2})(-[A-Z]{2})?$/; export function normalizeCode(code: string) { return code.replace(regExReplace, (match: string, p1: string, p2: string) => { const lang = p1.toLowerCase(); const local = p2.toUpperCase(); return local ? `${lang}-${local}` : lang; }); } export function isValidCode(code: string) { return langCodes.has(code); } export function lookupCode(code: string) { return langCodes.get(code); } ================================================ FILE: src/iso639-1/languageCodes.ts ================================================ export const codes: string[][] = [ // ['code', 'language'[', ''local']], ['af', 'Afrikaans'], ['af-NA', 'Afrikaans', 'Namibia'], ['af-ZA', 'Afrikaans', 'South Africa'], ['ak', 'Akan'], ['ak-GH', 'Akan', 'Ghana'], ['am', 'Amharic'], ['am-ET', 'Amharic', 'Ethiopia'], ['ar', 'Arabic'], ['ar-1', 'Arabic'], ['ar-AE', 'Arabic', 'United Arab Emirates'], ['ar-BH', 'Arabic', 'Bahrain'], ['ar-DJ', 'Arabic', 'Djibouti'], ['ar-DZ', 'Arabic', 'Algeria'], ['ar-EG', 'Arabic', 'Egypt'], ['ar-EH', 'Arabic'], ['ar-ER', 'Arabic', 'Eritrea'], ['ar-IL', 'Arabic', 'Israel'], ['ar-IQ', 'Arabic', 'Iraq'], ['ar-JO', 'Arabic', 'Jordan'], ['ar-KM', 'Arabic', 'Comoros'], ['ar-KW', 'Arabic', 'Kuwait'], ['ar-LB', 'Arabic', 'Lebanon'], ['ar-LY', 'Arabic', 'Libya'], ['ar-MA', 'Arabic', 'Morocco'], ['ar-MR', 'Arabic', 'Mauritania'], ['ar-OM', 'Arabic', 'Oman'], ['ar-PS', 'Arabic'], ['ar-QA', 'Arabic', 'Qatar'], ['ar-SA', 'Arabic', 'Saudi Arabia'], ['ar-SD', 'Arabic', 'Sudan'], ['ar-SO', 'Arabic', 'Somalia'], ['ar-SS', 'Arabic'], ['ar-SY', 'Arabic', 'Syria'], ['ar-TD', 'Arabic', 'Chad'], ['ar-TN', 'Arabic', 'Tunisia'], ['ar-YE', 'Arabic', 'Yemen'], ['as', 'Assamese'], ['as-IN', 'Assamese', 'India'], ['az', 'Azerbaijani'], ['az-AZ', 'Azerbaijani', 'Azerbaijan'], ['be', 'Belarusian'], ['be-BY', 'Belarusian', 'Belarus'], ['bg', 'Bulgarian'], ['bg-BG', 'Bulgarian', 'Bulgaria'], ['bm', 'Bambara'], ['bm-ML', 'Bambara', 'Mali'], ['bn', 'Bengali'], ['bn-BD', 'Bengali', 'Bangladesh'], ['bn-IN', 'Bengali', 'India'], ['bo', 'Tibetan'], ['bo-CN', 'Tibetan', 'China'], ['bo-IN', 'Tibetan', 'India'], ['br', 'Breton'], ['br-FR', 'Breton', 'France'], ['bs', 'Bosnian'], ['bs-BA', 'Bosnian', 'Bosnia and Herzegovina'], ['ca', 'Catalan'], ['ca-AD', 'Catalan', 'Andorra'], ['ca-ES', 'Catalan', 'Spain'], ['ca-FR', 'Catalan', 'France'], ['ca-IT', 'Catalan', 'Italy'], ['ce', 'Chechen'], ['ce-RU', 'Chechen', 'Russia'], ['cs', 'Czech'], ['cs-CZ', 'Czech', 'Czech Republic'], ['cu', 'Old Slavonic'], ['cu-RU', 'Old Slavonic', 'Russia'], ['cy', 'Welsh'], ['cy-GB', 'Welsh', 'United Kingdom'], ['da', 'Danish'], ['da-DK', 'Danish', 'Denmark'], ['da-GL', 'Danish', 'Greenland'], ['de', 'German'], ['de-AT', 'German', 'Austria'], ['de-BE', 'German', 'Belgium'], ['de-CH', 'German', 'Switzerland'], ['de-DE', 'German', 'Germany'], ['de-IT', 'German', 'Italy'], ['de-LI', 'German', 'Liechtenstein'], ['de-LU', 'German', 'Luxembourg'], ['dz', 'Dzongkha'], ['dz-BT', 'Dzongkha', 'Bhutan'], ['ee', 'Ewe'], ['ee-GH', 'Ewe', 'Ghana'], ['ee-TG', 'Ewe', 'Togo'], ['el', 'Greek', 'Modern (1453-)'], ['el-CY', 'Greek', 'Cyprus'], ['el-GR', 'Greek', 'Greece'], ['en', 'English'], ['en-AG', 'English', 'Antigua and Barbuda'], ['en-AI', 'English', 'Anguilla'], ['en-AS', 'English', 'American Samoa'], ['en-AT', 'English', 'Austria'], ['en-AU', 'English', 'Australia'], ['en-BB', 'English', 'Barbados'], ['en-BE', 'English', 'Belgium'], ['en-BI', 'English', 'Burundi'], ['en-BM', 'English', 'Bermuda'], ['en-BS', 'English', 'Bahamas'], ['en-BW', 'English', 'Botswana'], ['en-BZ', 'English', 'Belize'], ['en-CA', 'English', 'Canada'], ['en-CC', 'English', 'Cocos (Keeling) Islands'], ['en-CH', 'English', 'Switzerland'], ['en-CK', 'English', 'Cook Islands'], ['en-CM', 'English', 'Cameroon'], ['en-CX', 'English', 'Christmas Island'], ['en-CY', 'English', 'Cyprus'], ['en-DE', 'English', 'Germany'], ['en-DG', 'English'], ['en-DK', 'English', 'Denmark'], ['en-DM', 'English', 'Dominica'], ['en-ER', 'English', 'Eritrea'], ['en-FI', 'English', 'Finland'], ['en-FJ', 'English', 'Fiji'], ['en-FK', 'English', 'Falkland Islands (Islas Malvinas)'], ['en-FM', 'English', 'Micronesia'], ['en-GB', 'English', 'United Kingdom'], ['en-GD', 'English', 'Grenada'], ['en-GG', 'English', 'Guernsey'], ['en-GH', 'English', 'Ghana'], ['en-GI', 'English', 'Gibraltar'], ['en-GM', 'English', 'Gambia'], ['en-GU', 'English', 'Guam'], ['en-GY', 'English', 'Guyana'], ['en-HK', 'English', 'Hong Kong'], ['en-IE', 'English', 'Ireland'], ['en-IL', 'English', 'Israel'], ['en-IM', 'English', 'Isle of Man'], ['en-IN', 'English', 'India'], ['en-IO', 'English', 'British Indian Ocean Territory'], ['en-JE', 'English', 'Jersey'], ['en-JM', 'English', 'Jamaica'], ['en-KE', 'English', 'Kenya'], ['en-KI', 'English', 'Kiribati'], ['en-KN', 'English', 'Saint Kitts and Nevis'], ['en-KY', 'English', 'Cayman Islands'], ['en-LC', 'English', 'Saint Lucia'], ['en-LR', 'English', 'Liberia'], ['en-LS', 'English', 'Lesotho'], ['en-MG', 'English', 'Madagascar'], ['en-MH', 'English', 'Marshall Islands'], ['en-MO', 'English', 'Macau'], ['en-MP', 'English', 'Northern Mariana Islands'], ['en-MS', 'English', 'Montserrat'], ['en-MT', 'English', 'Malta'], ['en-MU', 'English', 'Mauritius'], ['en-MW', 'English', 'Malawi'], ['en-MY', 'English', 'Malaysia'], ['en-NA', 'English', 'Namibia'], ['en-NF', 'English', 'Norfolk Island'], ['en-NG', 'English', 'Nigeria'], ['en-NL', 'English', 'Netherlands'], ['en-NR', 'English', 'Nauru'], ['en-NU', 'English', 'Niue'], ['en-NZ', 'English', 'New Zealand'], ['en-PG', 'English', 'Papua New Guinea'], ['en-PH', 'English', 'Philippines'], ['en-PK', 'English', 'Pakistan'], ['en-PN', 'English', 'Pitcairn Islands'], ['en-PR', 'English', 'Puerto Rico'], ['en-PW', 'English', 'Palau'], ['en-RW', 'English', 'Rwanda'], ['en-SB', 'English', 'Solomon Islands'], ['en-SC', 'English', 'Seychelles'], ['en-SD', 'English', 'Sudan'], ['en-SE', 'English', 'Sweden'], ['en-SG', 'English', 'Singapore'], ['en-SH', 'English', 'Saint Helena'], ['en-SI', 'English', 'Slovenia'], ['en-SL', 'English', 'Sierra Leone'], ['en-SS', 'English'], ['en-SX', 'English'], ['en-SZ', 'English', 'Swaziland'], ['en-TC', 'English', 'Turks and Caicos Islands'], ['en-TK', 'English', 'Tokelau'], ['en-TO', 'English', 'Tonga'], ['en-TT', 'English', 'Trinidad and Tobago'], ['en-TV', 'English', 'Tuvalu'], ['en-TZ', 'English', 'Tanzania'], ['en-UG', 'English', 'Uganda'], ['en-UM', 'English', 'Baker Island'], ['en-US', 'English', 'United States'], ['en-VC', 'English', 'Saint Vincent and the Grenadines'], ['en-VG', 'English', 'British Virgin Islands'], ['en-VI', 'English', 'U.S. Virgin Islands'], ['en-VU', 'English', 'Vanuatu'], ['en-WS', 'English', 'Samoa'], ['en-ZA', 'English', 'South Africa'], ['en-ZM', 'English', 'Zambia'], ['en-ZW', 'English', 'Zimbabwe'], ['eo', 'Esperanto'], ['es', 'Spanish'], ['es-AR', 'Spanish', 'Argentina'], ['es-BO', 'Spanish', 'Bolivia'], ['es-BR', 'Spanish', 'Brazil'], ['es-BZ', 'Spanish', 'Belize'], ['es-CL', 'Spanish', 'Chile'], ['es-CO', 'Spanish', 'Colombia'], ['es-CR', 'Spanish', 'Costa Rica'], ['es-CU', 'Spanish', 'Cuba'], ['es-DO', 'Spanish', 'Dominican Republic'], ['es-EA', 'Spanish'], ['es-EC', 'Spanish', 'Ecuador'], ['es-ES', 'Spanish', 'Spain'], ['es-GQ', 'Spanish', 'Equatorial Guinea'], ['es-GT', 'Spanish', 'Guatemala'], ['es-HN', 'Spanish', 'Honduras'], ['es-IC', 'Spanish'], ['es-MX', 'Spanish', 'Mexico'], ['es-NI', 'Spanish', 'Nicaragua'], ['es-PA', 'Spanish', 'Panama'], ['es-PE', 'Spanish', 'Peru'], ['es-PH', 'Spanish', 'Philippines'], ['es-PR', 'Spanish', 'Puerto Rico'], ['es-PY', 'Spanish', 'Paraguay'], ['es-SV', 'Spanish', 'El Salvador'], ['es-US', 'Spanish', 'United States'], ['es-UY', 'Spanish', 'Uruguay'], ['es-VE', 'Spanish', 'Venezuela'], ['et', 'Estonian'], ['et-EE', 'Estonian', 'Estonia'], ['eu', 'Basque'], ['eu-ES', 'Basque', 'Spain'], ['fa', 'Persian'], ['fa-AF', 'Persian', 'Afghanistan'], ['fa-IR', 'Persian', 'Iran'], ['ff', 'Fulah'], ['ff-CM', 'Fulah', 'Cameroon'], ['ff-GN', 'Fulah', 'Guinea'], ['ff-MR', 'Fulah', 'Mauritania'], ['ff-SN', 'Fulah', 'Senegal'], ['fi', 'Finnish'], ['fi-FI', 'Finnish', 'Finland'], ['fo', 'Faroese'], ['fo-DK', 'Faroese', 'Denmark'], ['fo-FO', 'Faroese', 'Faroe Islands'], ['fr', 'French'], ['fr-BE', 'French', 'Belgium'], ['fr-BF', 'French', 'Burkina Faso'], ['fr-BI', 'French', 'Burundi'], ['fr-BJ', 'French', 'Benin'], ['fr-BL', 'French'], ['fr-CA', 'French', 'Canada'], ['fr-CD', 'French', 'Congo'], ['fr-CF', 'French', 'Central African Republic'], ['fr-CG', 'French', 'Congo'], ['fr-CH', 'French', 'Switzerland'], ['fr-CI', "French, Cote d'Ivoire (Ivory Coast)"], ['fr-CM', 'French', 'Cameroon'], ['fr-DJ', 'French', 'Djibouti'], ['fr-DZ', 'French', 'Algeria'], ['fr-FR', 'French', 'France'], ['fr-GA', 'French', 'Gabon'], ['fr-GF', 'French', 'French Guiana'], ['fr-GN', 'French', 'Guinea'], ['fr-GP', 'French', 'Saint Barthelemy'], ['fr-GQ', 'French', 'Equatorial Guinea'], ['fr-HT', 'French', 'Haiti'], ['fr-KM', 'French', 'Comoros'], ['fr-LU', 'French', 'Luxembourg'], ['fr-MA', 'French', 'Morocco'], ['fr-MC', 'French', 'Monaco'], ['fr-MF', 'French'], ['fr-MG', 'French', 'Madagascar'], ['fr-ML', 'French', 'Mali'], ['fr-MQ', 'French', 'Martinique'], ['fr-MR', 'French', 'Mauritania'], ['fr-MU', 'French', 'Mauritius'], ['fr-NC', 'French', 'New Caledonia'], ['fr-NE', 'French', 'Niger'], ['fr-PF', 'French', 'French Polynesia'], ['fr-PM', 'French', 'Saint Pierre and Miquelon'], ['fr-RE', 'French', 'Reunion'], ['fr-RW', 'French', 'Rwanda'], ['fr-SC', 'French', 'Seychelles'], ['fr-SN', 'French', 'Senegal'], ['fr-SY', 'French', 'Syria'], ['fr-TD', 'French', 'Chad'], ['fr-TG', 'French', 'Togo'], ['fr-TN', 'French', 'Tunisia'], ['fr-VU', 'French', 'Vanuatu'], ['fr-WF', 'French', 'Wallis and Futuna'], ['fr-YT', 'French', 'Mayotte'], ['fy', 'Western Frisian'], ['fy-NL', 'Western Frisian', 'Netherlands'], ['ga', 'Irish'], ['ga-IE', 'Irish', 'Ireland'], ['gd', 'Gaelic'], ['gd-GB', 'Gaelic', 'United Kingdom'], ['gl', 'Galician'], ['gl-ES', 'Galician', 'Spain'], ['gu', 'Gujarati'], ['gu-IN', 'Gujarati', 'India'], ['gv', 'Manx'], ['gv-IM', 'Manx', 'Isle of Man'], ['ha', 'Hausa'], ['ha-GH', 'Hausa', 'Ghana'], ['ha-NE', 'Hausa', 'Niger'], ['ha-NG', 'Hausa', 'Nigeria'], ['he', 'Hebrew'], ['he-IL', 'Hebrew', 'Israel'], ['hi', 'Hindi'], ['hi-IN', 'Hindi', 'India'], ['hr', 'Croatian'], ['hr-BA', 'Croatian', 'Bosnia and Herzegovina'], ['hr-HR', 'Croatian', 'Croatia'], ['hu', 'Hungarian'], ['hu-HU', 'Hungarian', 'Hungary'], ['hy', 'Armenian'], ['hy-AM', 'Armenian', 'Armenia'], ['id', 'Indonesian'], ['id-ID', 'Indonesian', 'Indonesia'], ['ig', 'Igbo'], ['ig-NG', 'Igbo', 'Nigeria'], ['ii', 'Sichuan Yi'], ['ii-CN', 'Sichuan Yi', 'China'], ['is', 'Icelandic'], ['is-IS', 'Icelandic', 'Iceland'], ['it', 'Italian'], ['it-CH', 'Italian', 'Switzerland'], ['it-IT', 'Italian', 'Italy'], ['it-SM', 'Italian', 'San Marino'], ['it-VA', 'Italian', 'Vatican City'], ['ja', 'Japanese'], ['ja-JP', 'Japanese', 'Japan'], ['ka', 'Georgian'], ['ka-GE', 'Georgian', 'Georgia'], ['ki', 'Kikuyu'], ['ki-KE', 'Kikuyu', 'Kenya'], ['kk', 'Kazakh'], ['kk-KZ', 'Kazakh', 'Kazakhstan'], ['kl', 'Kalaallisut'], ['kl-GL', 'Kalaallisut', 'Greenland'], ['km', 'Central Khmer'], ['km-KH', 'Central Khmer', 'Cambodia'], ['kn', 'Kannada'], ['kn-IN', 'Kannada', 'India'], ['ko', 'Korean'], ['ko-KP', 'Korean', 'Korea'], ['ko-KR', 'Korean', 'Korea'], ['ks', 'Kashmiri'], ['ks-IN', 'Kashmiri', 'India'], ['kw', 'Cornish'], ['kw-GB', 'Cornish', 'United Kingdom'], ['ky', 'Kirghiz'], ['ky-KG', 'Kirghiz', 'Kyrgyzstan'], ['lb', 'Luxembourgish'], ['lb-LU', 'Luxembourgish', 'Luxembourg'], ['lg', 'Ganda'], ['lg-UG', 'Ganda', 'Uganda'], ['ln', 'Lingala'], ['ln-AO', 'Lingala', 'Angola'], ['ln-CD', 'Lingala', 'Congo'], ['ln-CF', 'Lingala', 'Central African Republic'], ['ln-CG', 'Lingala', 'Congo'], ['lo', 'Lao'], ['lo-LA', 'Lao', 'Laos'], ['lt', 'Lithuanian'], ['lt-LT', 'Lithuanian', 'Lithuania'], ['lu', 'Luba-Katanga'], ['lu-CD', 'Luba-Katanga', 'Congo'], ['lv', 'Latvian'], ['lv-LV', 'Latvian', 'Latvia'], ['mg', 'Malagasy'], ['mg-MG', 'Malagasy', 'Madagascar'], ['mk', 'Macedonian'], ['mk-MK', 'Macedonian', 'Macedonia'], ['ml', 'Malayalam'], ['ml-IN', 'Malayalam', 'India'], ['mn', 'Mongolian'], ['mn-MN', 'Mongolian', 'Mongolia'], ['mr', 'Marathi'], ['mr-IN', 'Marathi', 'India'], ['ms', 'Malay'], ['ms-BN', 'Malay', 'Brunei'], ['ms-MY', 'Malay', 'Malaysia'], ['ms-SG', 'Malay', 'Singapore'], ['mt', 'Maltese'], ['mt-MT', 'Maltese', 'Malta'], ['my', 'Burmese'], ['my-MM', 'Burmese', 'Myanmar (Burma)'], ['nb', 'Bokmål Norwegian'], ['nb-NO', 'Bokmål Norwegian', 'Norway'], ['nb-SJ', 'Bokmål Norwegian', 'Svalbard'], ['nd', 'Ndebele, North'], ['nd-ZW', 'Ndebele, North', 'Zimbabwe'], ['ne', 'Nepali'], ['ne-IN', 'Nepali', 'India'], ['ne-NP', 'Nepali', 'Nepal'], ['nl', 'Dutch'], ['nl-AW', 'Dutch', 'Aruba'], ['nl-BE', 'Dutch', 'Belgium'], ['nl-BQ', 'Dutch'], ['nl-CW', 'Dutch'], ['nl-NL', 'Dutch', 'Netherlands'], ['nl-SR', 'Dutch', 'Suriname'], ['nl-SX', 'Dutch'], ['nn', 'Norwegian Nynorsk'], ['nn-NO', 'Norwegian Nynorsk', 'Norway'], ['om', 'Oromo'], ['om-ET', 'Oromo', 'Ethiopia'], ['om-KE', 'Oromo', 'Kenya'], ['or', 'Oriya'], ['or-IN', 'Oriya', 'India'], ['os', 'Ossetian'], ['os-GE', 'Ossetian', 'Georgia'], ['os-RU', 'Ossetian', 'Russia'], ['pa', 'Panjabi'], ['pa-IN', 'Panjabi', 'India'], ['pa-PK', 'Panjabi', 'Pakistan'], ['pl', 'Polish'], ['pl-PL', 'Polish', 'Poland'], ['ps', 'Pushto'], ['ps-AF', 'Pushto', 'Afghanistan'], ['pt', 'Portuguese'], ['pt-AO', 'Portuguese', 'Angola'], ['pt-BR', 'Portuguese', 'Brazil'], ['pt-CH', 'Portuguese', 'Switzerland'], ['pt-CV', 'Portuguese', 'Cape Verde'], ['pt-GQ', 'Portuguese', 'Equatorial Guinea'], ['pt-GW', 'Portuguese', 'Guinea-Bissau'], ['pt-LU', 'Portuguese', 'Luxembourg'], ['pt-MO', 'Portuguese', 'Macau'], ['pt-MZ', 'Portuguese', 'Mozambique'], ['pt-PT', 'Portuguese', 'Portugal'], ['pt-ST', 'Portuguese', 'Sao Tome and Principe'], ['pt-TL', 'Portuguese', 'Timor-Leste (East Timor)'], ['qu', 'Quechua'], ['qu-BO', 'Quechua', 'Bolivia'], ['qu-EC', 'Quechua', 'Ecuador'], ['qu-PE', 'Quechua', 'Peru'], ['rm', 'Romansh'], ['rm-CH', 'Romansh', 'Switzerland'], ['rn', 'Rundi'], ['rn-BI', 'Rundi', 'Burundi'], ['ro', 'Romanian'], ['ro-MD', 'Romanian', 'Moldova'], ['ro-RO', 'Romanian', 'Romania'], ['ru', 'Russian'], ['ru-BY', 'Russian', 'Belarus'], ['ru-KG', 'Russian', 'Kyrgyzstan'], ['ru-KZ', 'Russian', 'Kazakhstan'], ['ru-MD', 'Russian', 'Moldova'], ['ru-RU', 'Russian', 'Russia'], ['ru-UA', 'Russian', 'Ukraine'], ['rw', 'Kinyarwanda'], ['rw-RW', 'Kinyarwanda', 'Rwanda'], ['se', 'Northern Sami'], ['se-FI', 'Northern Sami', 'Finland'], ['se-NO', 'Northern Sami', 'Norway'], ['se-SE', 'Northern Sami', 'Sweden'], ['sg', 'Sango'], ['sg-CF', 'Sango', 'Central African Republic'], ['si', 'Sinhala'], ['si-LK', 'Sinhala', 'Sri Lanka'], ['sk', 'Slovak'], ['sk-SK', 'Slovak', 'Slovakia'], ['sl', 'Slovenian'], ['sl-SI', 'Slovenian', 'Slovenia'], ['sn', 'Shona'], ['sn-ZW', 'Shona', 'Zimbabwe'], ['so', 'Somali'], ['so-DJ', 'Somali', 'Djibouti'], ['so-ET', 'Somali', 'Ethiopia'], ['so-KE', 'Somali', 'Kenya'], ['so-SO', 'Somali', 'Somalia'], ['sq', 'Albanian'], ['sq-AL', 'Albanian', 'Albania'], ['sq-MK', 'Albanian', 'Macedonia'], ['sq-XK', 'Albanian'], ['sr', 'Serbian'], ['sr-BA', 'Serbian', 'Bosnia and Herzegovina'], ['sr-ME', 'Serbian', 'Montenegro'], ['sr-RS', 'Serbian', 'Serbia'], ['sr-XK', 'Serbian'], ['sv', 'Swedish'], ['sv-AX', 'Swedish', 'Aland'], ['sv-FI', 'Swedish', 'Finland'], ['sv-SE', 'Swedish', 'Sweden'], ['sw', 'Swahili'], ['sw-CD', 'Swahili', 'Congo'], ['sw-KE', 'Swahili', 'Kenya'], ['sw-TZ', 'Swahili', 'Tanzania'], ['sw-UG', 'Swahili', 'Uganda'], ['ta', 'Tamil'], ['ta-IN', 'Tamil', 'India'], ['ta-LK', 'Tamil', 'Sri Lanka'], ['ta-MY', 'Tamil', 'Malaysia'], ['ta-SG', 'Tamil', 'Singapore'], ['te', 'Telugu'], ['te-IN', 'Telugu', 'India'], ['th', 'Thai'], ['th-TH', 'Thai', 'Thailand'], ['ti', 'Tigrinya'], ['ti-ER', 'Tigrinya', 'Eritrea'], ['ti-ET', 'Tigrinya', 'Ethiopia'], ['tk', 'Turkmen'], ['tk-TM', 'Turkmen', 'Turkmenistan'], ['to', 'Tonga (Tonga Islands)'], ['to-TO', 'Tonga (Tonga Islands)', 'Tonga'], ['tr', 'Turkish'], ['tr-CY', 'Turkish', 'Cyprus'], ['tr-TR', 'Turkish', 'Turkey'], ['ug', 'Uighur'], ['ug-CN', 'Uighur', 'China'], ['uk', 'Ukrainian'], ['uk-UA', 'Ukrainian', 'Ukraine'], ['ur', 'Urdu'], ['ur-IN', 'Urdu', 'India'], ['ur-PK', 'Urdu', 'Pakistan'], ['uz', 'Uzbek'], ['uz-AF', 'Uzbek', 'Afghanistan'], ['uz-UZ', 'Uzbek', 'Uzbekistan'], ['vi', 'Vietnamese'], ['vi-VN', 'Vietnamese', 'Vietnam'], ['vo', 'Volapük'], ['yi', 'Yiddish'], ['yi-1', 'Yiddish'], ['yo', 'Yoruba'], ['yo-BJ', 'Yoruba', 'Benin'], ['yo-NG', 'Yoruba', 'Nigeria'], ['zh', 'Chinese'], ['zh-CN', 'Chinese', 'China'], ['zh-HK', 'Chinese', 'Hong Kong'], ['zh-MO', 'Chinese', 'Macau'], ['zh-SG', 'Chinese', 'Singapore'], ['zh-TW', 'Chinese', 'China'], ['zu', 'Zulu'], ['zu-ZA', 'Zulu', 'South Africa'], ]; ================================================ FILE: src/server/index.ts ================================================ export * from './server'; export * from './serverSettings'; ================================================ FILE: src/server/server.ts ================================================ export * from '../../server'; ================================================ FILE: src/server/serverSettings.ts ================================================ import * as server from './server'; import { normalizeCode } from '../iso639-1'; import * as util from '../util'; export function extractLanguage(config?: server.CSpellUserSettings): string[] | undefined { return ( config && config.language && normalizeToLocals(config.language) ) || undefined; } export function extractLocals(config: server.CSpellUserSettings = {}): string[] { return extractLocalsFromLanguageSettings(config.languageSettings); } export function extractLocalsFromLanguageSettings(langSettings: server.LanguageSetting[] = []): string[] { const locals = langSettings .map(s => s.local || '') .map(normalizeLocal) .join(','); return normalizeToLocals(locals); } export function extractDictionariesByLocal(config: server.CSpellUserSettings = {}): Map { return extractDictionariesByLocalLanguageSettings(config.languageSettings); } export function extractDictionariesByLocalLanguageSettings(langSettings: server.LanguageSetting[] = []): Map { const mapOfDict = new Map(); langSettings .map(({local, dictionaries = []}) => ({ local: normalizeLocal(local), dictionaries })) .filter(s => !!s.local) .filter(s => s.dictionaries.length > 0) .forEach(s => { s.local.split(',') .forEach(local => { mapOfDict.set( local, (mapOfDict.get(local) || []).concat(s.dictionaries).filter(util.uniqueFilter()) ); }); }); return mapOfDict; } export function normalizeLocal(local: string | string[] = ''): string { if(Array.isArray(local)) { local = local.join(','); } return normalizeToLocals(local).join(','); } export function normalizeToLocals(local: string = '') { return local .replace(/[|]/g, ',') .replace(/[*]/g, '') .split(',') .map(normalizeCode) .map(s => s.trim()) .filter(a => !!a) .filter(util.uniqueFilter()); } ================================================ FILE: src/settings/CSpellSettings.ts ================================================ import * as fs from 'fs-extra'; import * as json from 'comment-json'; import path = require('path'); import {CSpellUserSettingsWithComments} from '../server'; import { unique, uniqueFilter } from '../util'; const currentSettingsFileVersion = '0.1'; export const sectionCSpell = 'cSpell'; export const defaultFileName = 'cSpell.json'; export interface CSpellSettings extends CSpellUserSettingsWithComments { } // cSpell:ignore hte const defaultSettings: CSpellUserSettingsWithComments = { version: currentSettingsFileVersion, }; // cSpell:ignore hte const defaultSettingsWithComments: CSpellSettings = { ...defaultSettings, }; export function getDefaultSettings(): CSpellSettings { return Object.freeze(defaultSettings); } export function readSettings(filename: string): Promise { return fs.readFile(filename) .then( buffer => buffer.toString(), () => json.stringify(defaultSettingsWithComments, null, 4) ) .then(cfgJson => (json.parse(cfgJson) as CSpellSettings)) // covert parse errors into the defaultSettings .then(a => a, error => defaultSettingsWithComments) .then(settings => ({...defaultSettings, ...settings})); } export function updateSettings(filename: string, settings: CSpellSettings) { return fs.mkdirp(path.dirname(filename)) .then(() => fs.writeFile(filename, json.stringify(settings, null, 4))) .then(() => settings); } export function addWordToSettingsAndUpdate(filename: string, word: string) { return readApplyUpdateSettingsFile( filename, settings => addWordsToSettings(settings, word.split(' ')) ); } export function addWordsToSettings(settings: CSpellSettings, wordsToAdd: string[]) { const words = mergeWords(settings.words, wordsToAdd); return {...settings, words}; } export function addIgnoreWordToSettingsAndUpdate(filename: string, word: string) { return readApplyUpdateSettingsFile( filename, settings => addIgnoreWordsToSettings(settings, word.split(' ')) ); } export function addIgnoreWordsToSettings(settings: CSpellSettings, wordsToAdd: string[]) { const ignoreWords = mergeWords(settings.ignoreWords, wordsToAdd); return {...settings, ignoreWords}; } function mergeWords(wordsLeft: string[] | undefined, wordsRight: string[]): string[] { return (wordsLeft || []) .concat(wordsRight) .map(a => a.trim()) .filter(a => !!a) .filter(uniqueFilter()) .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); } export function removeWordsFromSettings(settings: CSpellSettings, wordsToRemove: string[]) { const words = filterOutWords(settings.words || [], wordsToRemove); return {...settings, words}; } export function filterOutWords(words: string[], wordsToRemove: string[]): string[] { const toRemove = new Set(wordsToRemove.map(w => w.toLowerCase())); return words.filter(w => !toRemove.has(w.toLowerCase())); } export function removeWordFromSettingsAndUpdate(filename: string, word: string) { return readApplyUpdateSettingsFile( filename, settings => removeWordsFromSettings(settings, word.split(' ')) ); } export function addLanguageIdsToSettings(settings: CSpellSettings, languageIds: string[], onlyIfExits: boolean) { if (settings.enabledLanguageIds || !onlyIfExits) { const enabledLanguageIds = unique((settings.enabledLanguageIds || []).concat(languageIds)); return { ...settings, enabledLanguageIds }; } return settings; } export function removeLanguageIdsFromSettings(settings: CSpellSettings, languageIds: string[]) { if (settings.enabledLanguageIds) { const excludeLangIds = new Set(languageIds); const enabledLanguageIds = settings.enabledLanguageIds.filter(a => !excludeLangIds.has(a)); const newSettings = {...settings, enabledLanguageIds}; if (!newSettings.enabledLanguageIds.length) { delete newSettings.enabledLanguageIds; } return newSettings; } return settings; } export function writeAddLanguageIdsToSettings(filename: string, languageIds: string[], onlyIfExits: boolean) { return readApplyUpdateSettingsFile( filename, settings => addLanguageIdsToSettings(settings, languageIds, onlyIfExits) ); } export function removeLanguageIdsFromSettingsAndUpdate(filename: string, languageIds: string[]) { return readApplyUpdateSettingsFile( filename, settings => removeLanguageIdsFromSettings(settings, languageIds) ); } export async function readApplyUpdateSettingsFile(filename: string, action: (settings: CSpellSettings) => CSpellSettings) { const settings = await readSettings(filename); const newSettings = action(settings); return updateSettings(filename, newSettings); } ================================================ FILE: src/settings/config.ts ================================================ import { workspace, Uri, ConfigurationTarget as Target } from 'coc.nvim'; import { TextDocument } from 'vscode-languageserver-protocol'; import { CSpellUserSettings } from '../server'; export { CSpellUserSettings } from '../server'; export { ConfigurationTarget, ConfigurationTarget as Target } from 'coc.nvim'; export const sectionCSpell = 'cSpell'; export interface InspectValues { defaultValue?: T | undefined; globalValue?: T | undefined; workspaceValue?: T | undefined; workspaceFolderValue?: T | undefined; } export const GlobalTarget = Target.Global; export const WorkspaceTarget = Target.Workspace; export interface ConfigTargetWithOptionalResource { target: Target; uri?: Uri; } export interface ConfigTargetWithResource extends ConfigTargetWithOptionalResource { uri: Uri; } export type ConfigTargetResourceFree = Target.Global | Target.Workspace; export type ConfigTarget = ConfigTargetResourceFree | ConfigTargetWithResource | ConfigTargetWithOptionalResource; export interface Inspect extends InspectValues { key: string; } export type Scope = keyof InspectValues; export type ScopeResourceFree = 'defaultValue' | 'globalValue' | 'workspaceValue'; export interface ScopeValues { Default: 'defaultValue'; Global: 'globalValue'; Workspace: 'workspaceValue'; Folder: 'workspaceFolderValue'; } export const Scopes: ScopeValues = { Default: 'defaultValue', Global: 'globalValue', Workspace: 'workspaceValue', Folder: 'workspaceFolderValue', }; export interface FullInspectScope { scope: Scope; resource: Uri | null; } export type InspectScope = FullInspectScope | ScopeResourceFree; /** * ScopeOrder from general to specific. */ const scopeOrder: Scope[] = [ 'defaultValue', 'globalValue', 'workspaceValue', 'workspaceFolderValue', ]; const scopeToOrderIndex = new Map( scopeOrder.map((s, i) => [s, i] as [string, number]) ); export type InspectResult = Inspect | undefined; export function getSectionName( subSection?: keyof CSpellUserSettings ): string { return [sectionCSpell, subSection].filter(a => !!a).join('.'); } export function getSettingsFromVSConfig( resource: Uri | null ): CSpellUserSettings { const config = getConfiguration(resource); return config.get(sectionCSpell, {}); } export function getSettingFromVSConfig( subSection: K, resource: Uri | null ): CSpellUserSettings[K] | undefined { const config = getConfiguration(resource); const settings = config.get(sectionCSpell, {}); return settings[subSection]; } /** * Inspect a scoped setting. It will not merge values. * @param subSection the cspell section * @param scope the scope of the value. A resource is needed to get folder level settings. */ export function inspectScopedSettingFromVSConfig( subSection: K, scope: InspectScope, ): CSpellUserSettings[K] | undefined { scope = normalizeScope(scope); const ins = inspectSettingFromVSConfig(subSection, scope.resource); return ins && ins[scope.scope]; } /** * Inspect a scoped setting. It will not merge values. * @param subSection the cspell section * @param scope the scope of the value. A resource is needed to get folder level settings. */ export function getScopedSettingFromVSConfig( subSection: K, scope: InspectScope, ): CSpellUserSettings[K] | undefined { return findScopedSettingFromVSConfig(subSection, scope).value; } /** * Inspect a scoped setting. It will not merge values. * @param subSection the cspell section * @param scope the scope of the value. A resource is needed to get folder level settings. */ export function findScopedSettingFromVSConfig( subSection: K, scope: InspectScope, ): FindBestConfigResult { scope = normalizeScope(scope); const ins = inspectSettingFromVSConfig(subSection, scope.resource); return findBestConfig(ins, scope.scope); } export function inspectSettingFromVSConfig( subSection: K, resource: Uri | null, ): Inspect { const config = inspectConfig(resource); const { defaultValue = {}, globalValue = {}, workspaceValue, workspaceFolderValue } = config; return { key: config.key + '.' + subSection, defaultValue: defaultValue[subSection], globalValue: globalValue[subSection], workspaceValue: workspaceValue ? workspaceValue[subSection] : workspaceFolderValue ? workspaceFolderValue[subSection] : undefined, workspaceFolderValue: workspaceFolderValue ? workspaceFolderValue[subSection] : workspaceValue ? workspaceValue[subSection] : undefined, }; } export function setSettingInVSConfig( subSection: K, value: CSpellUserSettings[K], configTarget: ConfigTarget, ): void { const target = extractTarget(configTarget); const uri = extractTargetUri(configTarget); const section = getSectionName(subSection); const config = getConfiguration(uri); return config.update(section, value, target === Target.Global); } export function inspectConfig( resource: Uri | null ): Inspect { const config = getConfiguration(resource); const settings = config.inspect(sectionCSpell) || { key: sectionCSpell }; return settings; } function toAny(value: any): any { return value; } export function isGlobalLevelTarget(target: ConfigTarget) { return isConfigTargetWithOptionalResource(target) && target.target === Target.Global || target === Target.Global; } export function isFolderLevelTarget(target: ConfigTarget) { return isConfigTargetWithResource(target) && target.target === Target.Workspace; } export function isConfigTargetWithResource(target: ConfigTarget): target is ConfigTargetWithResource { return isConfigTargetWithOptionalResource(target) && target.uri !== undefined; } export function isConfigTargetWithOptionalResource(target: ConfigTarget): target is ConfigTargetWithOptionalResource { return typeof target === 'object' && target.target !== undefined; } type TargetToScope = { [Target.Global]: 'globalValue'; [Target.User]: 'globalValue'; [Target.Workspace]: 'workspaceValue'; }; const targetToScope: TargetToScope = { 0: 'globalValue', 1: 'globalValue', 2: 'workspaceValue', }; export function configTargetToScope(target: ConfigTarget): InspectScope { if (isConfigTargetWithOptionalResource(target)) { return { scope: toScope(target.target), resource: target.uri || null, }; } return targetToScope[target]; } export function toScope(target: Target): Scope { return targetToScope[target]; } export function extractScope(inspectScope: InspectScope): Scope { if (isFullInspectScope(inspectScope)) { return inspectScope.scope; } return inspectScope; } function isFullInspectScope(scope: InspectScope): scope is FullInspectScope { return typeof scope === 'object'; } function normalizeScope(scope: InspectScope): FullInspectScope { if (isFullInspectScope(scope)) { return { scope: scope.scope, resource: scope.scope === Scopes.Folder || scope.scope === Scopes.Workspace ? normalizeResourceUri(scope.resource) : null, }; } return { scope, resource: null }; } function normalizeResourceUri(uri: Uri | null | undefined): Uri | null { if (uri) { const folder = workspace.getWorkspaceFolder(uri.toString()); return folder && Uri.parse(folder.uri) || null; } return null; } export interface FindBestConfigResult { scope: Scope; value: CSpellUserSettings[K]; } function findBestConfig( config: Inspect, scope: Scope, ): FindBestConfigResult { for (let p = scopeToOrderIndex.get(scope)!; p >= 0; p -= 1) { const k = scopeOrder[p]; const v = config[k]; if (v !== undefined) { return { scope: k, value: v }; } } return { scope: 'defaultValue', value: undefined }; } export function isGlobalTarget(target: ConfigTarget): boolean { return extractTarget(target) === Target.Global; } export function createTargetForUri(target: Target, uri: Uri): ConfigTargetWithResource { return { target, uri }; } export function createTargetForDocument(target: Target, doc: TextDocument): ConfigTargetWithResource { return createTargetForUri(target, Uri.parse(doc.uri)); } export function extractTarget(target: ConfigTarget): Target { return isConfigTargetWithOptionalResource(target) ? target.target : target; } export function extractTargetUri(target: ConfigTarget): Uri | null { return isConfigTargetWithResource(target) ? target.uri : null; } export function getConfiguration(uri?: Uri | null) { return fetchConfiguration(uri); } function fetchConfiguration(uri?: Uri | null) { return workspace.getConfiguration(undefined, toAny(uri)); } ================================================ FILE: src/settings/index.ts ================================================ export * from './settings'; export * from './config'; export {CSpellSettings} from './CSpellSettings'; ================================================ FILE: src/settings/languageIds.ts ================================================ export const languageIds: string[] = [ 'asciidoc', 'bat', 'c', 'clojure', 'coffeescript', 'cpp', 'csharp', 'css', 'diff', 'dockerfile', 'fsharp', 'git-commit', 'git-rebase', 'go', 'groovy', 'handlebars', 'html', 'ini', 'jade', 'java', 'javascript', 'javascriptreact', 'json', 'less', 'lua', 'makefile', 'markdown', 'objective-c', 'perl', 'perl6', 'php', 'plaintext', 'powershell', 'properties', 'pug', 'python', 'r', 'razor', 'ruby', 'rust', 'scss', 'shaderlab', 'shellscript', 'sql', 'swift', 'typescript', 'typescriptreact', 'vb', 'xml', 'xsl', 'yaml', ]; ================================================ FILE: src/settings/settings.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import { Uri, Disposable, workspace, ConfigurationTarget } from 'coc.nvim'; import { CSpellUserSettings, normalizeLocal } from '../server'; import * as CSpellSettings from './CSpellSettings'; import { unique } from '../util'; import * as watcher from '../util/watcher'; import * as config from './config'; import { InspectScope } from './config'; export { ConfigTarget, InspectScope, Scope } from './config'; export const baseConfigName = CSpellSettings.defaultFileName; export const configFileLocations = [ baseConfigName, baseConfigName.toLowerCase(), `.${baseConfigName.toLowerCase()}`, `.vim/${baseConfigName}`, `.vim/${baseConfigName.toLowerCase()}`, `.vscode/${baseConfigName}`, `.vscode/${baseConfigName.toLowerCase()}`, ]; export interface SettingsInfo { path: string; settings: CSpellUserSettings; } export function watchSettingsFiles(callback: () => void): Disposable { // Every 10 seconds see if we have new files to watch. let busy = false; const intervalObj = setInterval(async () => { if (busy) { return; } busy = true; const settingsFiles = await findSettingsFiles(); settingsFiles .map(uri => uri.fsPath) .filter(file => !watcher.isWatching(file)) .forEach(file => watcher.add(file, callback)); busy = false; }, 10000); return { dispose: () => { watcher.dispose(); clearInterval(intervalObj); } }; } export function getDefaultWorkspaceConfigLocation() { const { workspaceFolders } = workspace; const root = workspaceFolders && workspaceFolders[0] && Uri.parse(workspaceFolders[0].uri).fsPath; return root ? path.join(root, baseConfigName) : undefined; } export function hasWorkspaceLocation() { const { workspaceFolders } = workspace; return !!(workspaceFolders && workspaceFolders[0]); } export function findSettingsFiles(uri?: Uri): Promise { const { workspaceFolders } = workspace; if (!workspaceFolders || !hasWorkspaceLocation()) { return Promise.resolve([]); } const folders = uri ? [workspace.getWorkspaceFolder(uri.toString())!].filter(a => !!a) : workspaceFolders; const possibleLocations = folders .map(folder => Uri.parse(folder.uri).fsPath) .map(root => configFileLocations.map(rel => path.join(root, rel))) .reduce((a, b) => a.concat(b)); const found = possibleLocations .map(filename => fs.pathExists(filename) .then(exists => ({ filename, exists }))); return Promise.all(found).then(found => found .filter(found => found.exists) .map(found => found.filename) .map(filename => Uri.file(filename)) ); } export function findExistingSettingsFileLocation(uri?: Uri): Promise { return findSettingsFiles(uri) .then(uris => uris.map(uri => uri.fsPath)) .then(paths => paths[0]); } export function findSettingsFileLocation(): Promise { return findExistingSettingsFileLocation() .then(path => path || getDefaultWorkspaceConfigLocation()); } export function loadTheSettingsFile(): Promise { return findSettingsFileLocation() .then(loadSettingsFile); } export function loadSettingsFile(path: string): Promise { return path ? CSpellSettings.readSettings(path).then(settings => (path ? { path, settings } : undefined)) : Promise.resolve(undefined); } export function setEnableSpellChecking(target: config.ConfigTarget, enabled: boolean): void { return config.setSettingInVSConfig('enabled', enabled, target); } export function getEnabledLanguagesFromConfig(scope: InspectScope) { return config.getScopedSettingFromVSConfig('enabledLanguageIds', scope) || []; } /** * @description Enable a programming language * @param target - which level of setting to set * @param languageId - the language id, e.g. 'typescript' */ export async function enableLanguage(target: config.ConfigTarget, languageId: string): Promise { await enableLanguageIdForTarget(languageId, true, target, true); } export async function disableLanguage(target: config.ConfigTarget, languageId: string): Promise { await enableLanguageIdForTarget(languageId, false, target, true); } export function addWordToSettings(target: config.ConfigTarget, word: string) { const useGlobal = config.isGlobalTarget(target) || !hasWorkspaceLocation(); const addWords = word.split(' '); const section: 'userWords' | 'words' = useGlobal ? 'userWords' : 'words'; return updateSettingInConfig( section, target, words => unique(addWords.concat(words || []).sort()), true ); } export function addIgnoreWordToSettings(target: config.ConfigTarget, word: string) { const addWords = word.split(' '); return updateSettingInConfig( 'ignoreWords', target, words => unique(addWords.concat(words || []).sort()), true ); } export async function removeWordFromSettings(target: config.ConfigTarget, word: string) { const useGlobal = config.isGlobalTarget(target); const section: 'userWords' | 'words' = useGlobal ? 'userWords' : 'words'; const toRemove = word.split(' '); return updateSettingInConfig( section, target, words => CSpellSettings.filterOutWords(words || [], toRemove), true ); } export function toggleEnableSpellChecker(target: config.ConfigTarget): void { const resource = config.isConfigTargetWithResource(target) ? target.uri : null; const curr = config.getSettingFromVSConfig('enabled', resource); return config.setSettingInVSConfig('enabled', !curr, target); } /** * Enables the current programming language of the active file in the editor. */ export async function enableCurrentLanguage(): Promise { const doc = await workspace.document; if (doc && doc.textDocument && doc.textDocument.languageId) { const target = config.createTargetForDocument(ConfigurationTarget.Workspace, doc.textDocument); return enableLanguage(target, doc.textDocument.languageId); } return Promise.resolve(); } /** * Disables the current programming language of the active file in the editor. */ export async function disableCurrentLanguage(): Promise { const doc = await workspace.document; if (doc && doc.textDocument && doc.textDocument.languageId) { const target = config.createTargetForDocument(ConfigurationTarget.Workspace, doc.textDocument); return disableLanguage(target, doc.textDocument.languageId); } return Promise.resolve(); } export async function enableLocal(target: config.ConfigTarget, local: string) { await enableLocalForTarget(local, true, target, true); } export async function disableLocal(target: config.ConfigTarget, local: string) { await enableLocalForTarget(local, false, target, true); } export function enableLocalForTarget( local: string, enable: boolean, target: config.ConfigTarget, isCreateAllowed: boolean ): Promise { const applyFn: (src: string | undefined) => string | undefined = enable ? (currentLanguage) => unique(normalizeLocal(currentLanguage).split(',').concat(local.split(','))).join(',') : (currentLanguage) => { const value = unique(normalizeLocal(currentLanguage).split(',')).filter(lang => lang !== local).join(','); return value || undefined; }; return updateSettingInConfig( 'language', target, applyFn, isCreateAllowed, shouldUpdateCSpell(target) ); } export function enableLanguageIdForTarget( languageId: string, enable: boolean, target: config.ConfigTarget, isCreateAllowed: boolean ): Promise { const fn: (src: string[] | undefined) => string[] | undefined = enable ? (src) => unique([languageId].concat(src || [])).sort() : (src) => { const v = src && unique(src.filter(v => v !== languageId)).sort(); return v && v.length > 0 && v || undefined; }; return updateSettingInConfig( 'enabledLanguageIds', target, fn, isCreateAllowed, shouldUpdateCSpell(target) ); } /** * Try to enable / disable a programming language id starting at folder level going to global level, stopping when successful. * @param languageId * @param enable * @param uri */ export async function enableLanguageIdForClosestTarget( languageId: string, enable: boolean, uri: Uri | undefined ): Promise { if (languageId) { if (uri) { // Apply it to the workspace folder if it exists. const target: config.ConfigTargetWithResource = { target: ConfigurationTarget.Workspace, uri, }; if (await enableLanguageIdForTarget(languageId, enable, target, false)) return; } if (workspace.workspaceFolders && workspace.workspaceFolders.length && await enableLanguageIdForTarget(languageId, enable, config.Target.Workspace, false) ) { return; } // Apply it to User settings. await enableLanguageIdForTarget(languageId, enable, config.Target.Global, true); } return; } /** * Determine if we should update the cspell file if it exists. * 1. Update is allowed for WorkspaceFolders * 1. Update is allowed for Workspace if there is only 1 folder. * 1. Update is not allowed for the Global target. * @param target */ function shouldUpdateCSpell(target: config.ConfigTarget) { const cfgTarget = config.extractTarget(target); return cfgTarget !== config.Target.Global && workspace.workspaceFolders && (cfgTarget === config.Target.Workspace || workspace.workspaceFolders.length === 1); } /** * Update Config Settings. * Writes to both the VS Config and the `cspell.json` if it exists. * If a `cspell.json` exists, it will be preferred over the VS Code config setting. * @param section the configuration value to set/update. * @param target the configuration level (Global, Workspace, WorkspaceFolder) * @param applyFn the function to calculate the new value. * @param create if the setting does not exist, then create it. * @param updateCSpell update the cspell.json file if it exists. */ export async function updateSettingInConfig( section: K, target: config.ConfigTarget, applyFn: (origValue: CSpellUserSettings[K]) => CSpellUserSettings[K], create: boolean, updateCSpell: boolean = true ): Promise { interface Result { value: CSpellUserSettings[K] | undefined; } const scope = config.configTargetToScope(target); const orig = config.findScopedSettingFromVSConfig(section, scope); const uri = config.isConfigTargetWithOptionalResource(target) && target.uri || undefined; const settingsFilename = updateCSpell && !config.isGlobalLevelTarget(target) && await findExistingSettingsFileLocation(uri) || undefined; async function updateConfig(): Promise { if (create || orig.value !== undefined && orig.scope === config.extractScope(scope)) { const newValue = applyFn(orig.value); await config.setSettingInVSConfig(section, newValue, target); return { value: newValue }; } return false; } async function updateCSpellFile(settingsFilename: string | undefined, defaultValue: CSpellUserSettings[K] | undefined): Promise { if (settingsFilename) { await CSpellSettings.readApplyUpdateSettingsFile(settingsFilename, settings => { const v = settings[section]; const newValue = v !== undefined ? applyFn(v) : applyFn(defaultValue); const newSettings = {...settings }; if (newValue === undefined) { delete newSettings[section]; } else { newSettings[section] = newValue; } return newSettings; }); return true; } return false; } const configResult = await updateConfig(); const cspellResult = await updateCSpellFile(settingsFilename, orig.value); return [ !!configResult, cspellResult ].reduce((a, b) => a || b, false); } ================================================ FILE: src/statusbar.ts ================================================ import * as coc from 'coc.nvim'; import {TextDocument} from 'vscode-languageserver-protocol'; import {CSpellUserSettings} from './server'; import { CSpellClient } from './client'; import { isSupportedDoc } from './util'; export function initStatusBar(context: coc.ExtensionContext, client: CSpellClient) { const statusText = coc.workspace.getConfiguration('cSpell').get('status-bar-text', 'cSpell'); const sbCheck = coc.workspace.createStatusBarItem(999, { progress: true }); sbCheck.text = statusText; sbCheck.show(); function updateStatusBarWithSpellCheckStatus(document?: TextDocument) { if (!document) return; sbCheck.text = statusText; sbCheck.isProgress = true; sbCheck.show(); const { uri } = document; client.isSpellCheckEnabled(document) .then(async (response) => { sbCheck.isProgress = false; const doc = await coc.workspace.document; const document = doc && doc.textDocument; const docUri = document && document.uri; if (docUri !== uri) { return; } const { languageEnabled = false, fileEnabled = false } = response; const isChecked = languageEnabled && fileEnabled; if (isChecked) { sbCheck.show(); } else { sbCheck.hide(); } }); } function updateStatusBar(doc?: TextDocument) { if (!isSupportedDoc(doc)) { sbCheck.hide(); return; } const document = doc; const settings: CSpellUserSettings = coc.workspace.getConfiguration().get('cSpell') as CSpellUserSettings; const { enabled, showStatus = true } = settings; if (!showStatus) { sbCheck.hide(); return; } if (enabled) { updateStatusBarWithSpellCheckStatus(document); } else { sbCheck.hide(); } } async function onDidChange() { const doc = await coc.workspace.document; updateStatusBar(doc && doc.textDocument); } context.subscriptions.push( coc.workspace.registerAutocmd({ event: 'BufEnter', request: false, callback: onDidChange, }), coc.workspace.onDidChangeConfiguration(onDidChange), sbCheck ); onDidChange(); } ================================================ FILE: src/util/commonPrefix.ts ================================================ export function commonPrefix(values: string[]): string { if (!values.length) return ''; const min = values.reduce((min, curr) => (min <= curr ? min : curr)); const max = values.reduce((max, curr) => (max >= curr ? max : curr)); return pfx(min, max); } function pfx(a: string, b: string): string { const s = Math.min(a.length, b.length); let i = 0; while (i < s && a[i] === b[i]) { i++; } return a.slice(0, i); } ================================================ FILE: src/util/index.ts ================================================ export * from './util'; export * from './uriHelper'; ================================================ FILE: src/util/pipe.ts ================================================ export function defaultTo(value: T): (v: T | undefined) => T { return (v: T | undefined) => v === undefined ? value : v; } export type Nested = Exclude; export type NestedKey = keyof Nested; export function extract(key: K): (t: T | undefined) => T[K] | undefined; export function extract>(key: K, k2: K2): (t: T | undefined) => Nested[K2] | undefined; export function extract, K3 extends NestedKey, K2>>(key: K, k2: K2, k3: K3): (t: T | undefined) => Nested, K2>[K3] | undefined; export function extract, K3 extends NestedKey, K2>, K4 extends NestedKey, K2>, K3>>(key: K, k2: K2, k3: K3, k4: K4): (t: T | undefined) => Nested, K2>, K3>[K4] | undefined; export function extract(key: K): (t: T | undefined) => T[K] | undefined { if (arguments.length > 1) { const args = [...arguments]; return (t: T | undefined) => { let v = t as any; for (const k of args) { v = v === undefined ? undefined : v[k]; } return v; }; } return (t: T | undefined) => t === undefined ? undefined : t[key]; } export function map(fn: (t: T) => R): (t: T | undefined) => R | undefined { return (t: T | undefined) => t === undefined ? undefined : fn(t); } export function pipe(t: T): T; export function pipe(t: T, fn: (t: T) => R): R; export function pipe(t: T, fn: (t: T) => R, fn2: (t: R) => S): S; export function pipe(t: T, fn: (t: T) => R, fn2: (t: R) => S, fn3: (t: S) => A): A; export function pipe(t: T, fn: (t: T) => R, fn2: (t: R) => S, fn3: (t: S) => A, fn4: (t: A) => B): B; export function pipe(t: T, fn: (t: T) => R, fn2: (t: R) => S, fn3: (t: S) => A, fn4: (t: A) => B, fn5: (t: B) => C): C; export function pipe(t: T): T { if (arguments.length > 1) { const fns = [...arguments].slice(1) as ((v: any) => any)[]; let v = t as any; for (const fn of fns) { v = fn(v); } return v; } return t; } ================================================ FILE: src/util/uriHelper.ts ================================================ import { TextDocument } from 'vscode-languageserver-protocol'; import * as coc from 'coc.nvim'; export const supportedSchemes = ['file', 'untitled']; export const setOfSupportedSchemes = new Set(supportedSchemes); export function isSupportedUri(uri?: coc.Uri): boolean { return !!uri && setOfSupportedSchemes.has(uri.scheme); } export function isSupportedDoc(doc?: TextDocument): boolean { return !!doc && isSupportedUri(coc.Uri.parse(doc.uri)); } ================================================ FILE: src/util/util.ts ================================================ export function unique(values: T[]): T[] { return [...(new Set(values))]; } export function uniqueFilter() { const seen = new Set(); return (v: T) => !!(!seen.has(v) && seen.add(v)); } export function freqCount(values: T[]): [T, number][] { const map = new Map(); values.forEach(v => map.set(v, (map.get(v) || 0) + 1)); return [...map.entries()]; } export type Maybe = T | undefined; ================================================ FILE: src/util/watcher.ts ================================================ const watch = require('node-watch'); export type Events = 'update' | 'remove' | 'error'; interface Watcher { close: () => void; } export type Callback = (name: string, event: Events) => void; interface FileWatcher { watcher: Watcher; callbacks: Set; } const watchedFiles = new Map(); function listener(event: Events, name: string) { const watcher = watchedFiles.get(name); if (watcher) { watcher.callbacks.forEach(fn => fn(name, event)); } } export function isWatching(fileName: string) { return !!watchedFiles.get(fileName); } export function stopWatching(fileName: string) { const watcher = watchedFiles.get(fileName); if (watcher) { watchedFiles.delete(fileName); watcher.watcher.close(); } } export function add(fileName: string, callback: Callback) { if (!watchedFiles.has(fileName)) { watchedFiles.set(fileName, { watcher: watch(fileName, listener) as Watcher, callbacks: new Set(), }); } const watcher = watchedFiles.get(fileName); watcher!.callbacks.add(callback); } export function dispose() { for (const w of watchedFiles.values()) { w.watcher.close(); } watchedFiles.clear(); } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "es2017", "strictNullChecks": true, "strict": true, "strictFunctionTypes": false, "module": "commonjs", "moduleResolution": "node", "outDir": "out", "sourceMap": false, "declaration": false, "declarationMap": false, "rootDir": "src" }, "include": [ "src" ], "exclude": [ "./out" ] } ================================================ FILE: tslint.json ================================================ { "rules": { "class-name": true, "comment-format": [ true, "check-space" ], "indent": [ true, "spaces" ], "no-duplicate-variable": true, "no-eval": true, "no-internal-module": true, "no-trailing-whitespace": true, "no-var-keyword": true, "one-line": [ true, "check-open-brace", "check-whitespace" ], "quotemark": [ true, "single", "jsx-double", "avoid-escape" ], "semicolon": [ true, "always" ], "triple-equals": [ true, "allow-null-check" ], "typedef-whitespace": [ true, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" } ], "variable-name": [ true, "ban-keywords" ], "whitespace": [ true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type" ], "max-line-length": [ true, 140 ], "no-unused-variable": true } } ================================================ FILE: webpack.config.js ================================================ const path = require('path'); /** @type {import('webpack').Configuration} */ module.exports = { entry: { index: './src/index.ts' }, target: 'node', mode: 'production', resolve: { mainFields: ['module', 'main'], extensions: ['.js', '.ts'], }, externals: { 'coc.nvim': 'commonjs coc.nvim', 'cspell-dict-vimlang': 'commonjs cspell-dict-vimlang', }, module: { rules: [ { test: /\.ts$/, exclude: /node_modules/, use: [ { loader: 'ts-loader', }, ], }, ], }, output: { path: path.join(__dirname, 'out'), filename: '[name].js', libraryTarget: 'commonjs', }, plugins: [], node: { __dirname: false, __filename: false, }, };