Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit 8b7ab9c

Browse files
committed
Changed spell-checking to be plugin-based.
* Changed the package to allow for external packages to provide additional checking. (Closes #74) - Disabled the task-based handling because of passing plugins. - Two default plugins are included: system-based dictionaries and "known words". - Suggestions and "add to dictionary" are also provided via interfaces. (Closes #10) - Modified various calls so they are aware of the where the buffer is located. * Modified system to allow for multiple plugins/checkers to identify correctness. - Incorrect words must be incorrect for all checkers. - Any checker that treats a word as valid is considered valid for the buffer. * Extracted system-based dictionary support into separate checker. - System dictionaries can now check across multiple system locales. - Locale selection can be changed via package settings. (Closes #21) - Multiple locales can be selected. (Closes #11) - External search paths can be used for Linux and OS X. - Default language is based on the process environment, with a fallback to the browser, before finally using `en-US` as a fallback. * Extracted hard-coded approved list into a separate checker. - User can add additional "known words" via settings. - Added an option to add more known words via the suggestion dialog. * Updated ignore files and added EditorConfig settings for development. * Various coffee-centric formatting.
1 parent ece2e24 commit 8b7ab9c

15 files changed

+783
-82
lines changed

.editorconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# EditorConfig is awesome: http://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
[*]
7+
indent_size = 2
8+
indent_style = space
9+
insert_final_newline = true
10+
max_line_length = 80
11+
tab_width = 2
12+
trim_trailing_whitespace = true
13+
14+
[*.{js,ts,coffee}]
15+
quote_type = single

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
*~
2+
npm-debug.log
13
node_modules
24
.DS_Store

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,45 @@ for the _Spell Check_ package. Here are some examples: `source.coffee`,
2727
## Changing the dictionary
2828

2929
Currently, only the English (US) dictionary is supported. Follow [this issue](https://github.com/atom/spell-check/issues/11) for updates.
30+
31+
## Writing Providers
32+
33+
The `spell-check` allows for additional dictionaries to be used at the same time using Atom's `providedServices` element in the `package.json` file.
34+
35+
"providedServices": {
36+
"spell-check": {
37+
"versions": {
38+
"1.0.0": "nameOfFunctionToProvideSpellCheck"
39+
}
40+
}
41+
}
42+
43+
The `nameOfFunctionToProvideSpellCheck` function may return either a single object describing the spell-check plugin or an array of them. Each spell-check plugin must implement the following:
44+
45+
* getId(): string
46+
* This returns the canonical identifier for this plugin. Typically, this will be the package name with an optional suffix for options, such as `spell-check-project` or `spell-check:en-US`. This identifier will be used for some control plugins (such as `spell-check-project`) to enable or disable the plugin.
47+
* getName(): string
48+
* Returns the human-readable name for the plugin. This is used on the status screen and in various dialogs/popups.
49+
* getPriority(): number
50+
* Determines how significant the plugin is for information with lower numbers being more important. Typically, user-entered data (such as the config `knownWords` configuration or a project's dictionary) will be lower than system data (priority 100).
51+
* isEnabled(): boolean
52+
* If this returns true, then the plugin will considered for processing.
53+
* getStatus(): string
54+
* Returns a string that describes the current status or state of the plugin. This is to allow a plugin to identify why it is disabled or to indicate version numbers. This can be formatted for Markdown, including links, and will be displayed on a status screen (eventually).
55+
* providesSpelling(buffer): boolean
56+
* If this returns true, then the plugin will be included when looking for incorrect and correct words via the `check` function.
57+
* check(buffer, text: string): { correct: [range], incorrect: [range] }
58+
* `correct` and `incorrect` are both optional. If they are skipped, then it means the plugin does not contribute to the correctness or incorrectness of any word. If they are present but empty, it means there are no correct or incorrect words respectively.
59+
* The `range` objects have a signature of `{ start: X, end: Y }`.
60+
* providesSuggestions(buffer): boolean
61+
* If this returns true, then the plugin will be included when querying for suggested words via the `suggest` function.
62+
* suggest(buffer, word: string): [suggestion: string]
63+
* Returns a list of suggestions for a given word ordered so the most important is at the beginning of the list.
64+
* providesAdding(buffer): boolean
65+
* If this returns true, then the dictionary allows a word to be added to the dictionary.
66+
* getAddingTargets(buffer): [target]
67+
* Gets a list of targets to show to the user.
68+
* The `target` object has a minimum signature of `{ label: stringToShowTheUser }`. For example, `{ label: "Ignore word (case-sensitive)" }`.
69+
* This is a list to allow plugins to have multiple options, such as adding it as a case-sensitive or insensitive, temporary verses configuration, etc.
70+
* add(buffer, target, word)
71+
* Adds a word to the dictionary, using the target for identifying which one is used.

lib/corrections-view.coffee

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
module.exports =
55
class CorrectionsView extends SelectListView
6-
initialize: (@editor, @corrections, @marker) ->
6+
initialize: (@editor, @corrections, @marker, @updateTarget, @updateCallback) ->
77
super
8-
@addClass('corrections popover-list')
8+
@addClass('spell-check-corrections corrections popover-list')
99
@attach()
1010

1111
attach: ->
@@ -20,22 +20,51 @@ class CorrectionsView extends SelectListView
2020
@cancel()
2121
@remove()
2222

23-
confirmed: (correction) ->
23+
confirmed: (item) ->
2424
@cancel()
25-
return unless correction
25+
return unless item
2626
@editor.transact =>
27-
@editor.setSelectedBufferRange(@marker.getBufferRange())
28-
@editor.insertText(correction)
27+
if item.isSuggestion
28+
# Update the buffer with the correction.
29+
@editor.setSelectedBufferRange(@marker.getRange())
30+
@editor.insertText(item.suggestion)
31+
else
32+
# Build up the arguments object for this buffer and text.
33+
projectPath = null
34+
relativePath = null
35+
if @editor.buffer?.file?.path
36+
[projectPath, relativePath] = atom.project.relativizePath(@editor.buffer.file.path)
37+
args = {
38+
id: @id,
39+
projectPath: projectPath,
40+
relativePath: relativePath
41+
}
42+
43+
# Send the "add" request to the plugin.
44+
item.plugin.add args, item
45+
46+
# Update the buffer to handle the corrections.
47+
@updateCallback.bind(@updateTarget)()
2948

3049
cancelled: ->
3150
@overlayDecoration.destroy()
3251
@restoreFocus()
3352

34-
viewForItem: (word) ->
35-
element = document.createElement('li')
36-
element.textContent = word
53+
viewForItem: (item) ->
54+
element = document.createElement "li"
55+
if item.isSuggestion
56+
# This is a word replacement suggestion.
57+
element.textContent = item.label
58+
else
59+
# This is an operation such as add word.
60+
em = document.createElement "em"
61+
em.textContent = item.label
62+
element.appendChild em
3763
element
3864

65+
getFilterKey: ->
66+
"label"
67+
3968
selectNextItemView: ->
4069
super
4170
false

lib/known-words-checker.coffee

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
class KnownWordsChecker
2+
enableAdd: false
3+
spelling: null
4+
checker: null
5+
6+
constructor: (knownWords) ->
7+
# Set up the spelling manager we'll be using.
8+
spellingManager = require "spelling-manager"
9+
@spelling = new spellingManager.TokenSpellingManager
10+
@checker = new spellingManager.BufferSpellingChecker @spelling
11+
12+
# Set our known words.
13+
@setKnownWords knownWords
14+
15+
deactivate: ->
16+
console.log(@getid() + "deactivating")
17+
18+
getId: -> "spell-check:known-words"
19+
getName: -> "Known Words"
20+
getPriority: -> 10
21+
isEnabled: -> true
22+
getStatus: -> "Working correctly."
23+
providesSpelling: (args) -> true
24+
providesSuggestions: (args) -> true
25+
providesAdding: (args) -> @enableAdd
26+
27+
check: (args, text) ->
28+
ranges = []
29+
checked = @checker.check text
30+
for token in checked
31+
if token.status is 1
32+
ranges.push {start: token.start, end: token.end}
33+
{correct: ranges}
34+
35+
suggest: (args, word) ->
36+
@spelling.suggest word
37+
38+
getAddingTargets: (args) ->
39+
if @enableAdd
40+
[{sensitive: false, label: "Add to " + @getName()}]
41+
else
42+
[]
43+
44+
add: (args, target) ->
45+
c = atom.config.get 'spell-check.knownWords'
46+
c.push target.word
47+
atom.config.set 'spell-check.knownWords', c
48+
49+
setAddKnownWords: (newValue) ->
50+
@enableAdd = newValue
51+
52+
setKnownWords: (knownWords) ->
53+
# Clear out the old list.
54+
@spelling.sensitive = {}
55+
@spelling.insensitive = {}
56+
57+
# Add the new ones into the list.
58+
if knownWords
59+
for ignore in knownWords
60+
@spelling.add ignore
61+
62+
module.exports = KnownWordsChecker

lib/main.coffee

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,52 @@ SpellCheckView = null
22
spellCheckViews = {}
33

44
module.exports =
5+
instance: null
6+
57
activate: ->
8+
# Create the unified handler for all spellchecking.
9+
SpellCheckerManager = require './spell-check-manager.coffee'
10+
@instance = SpellCheckerManager
11+
that = this
12+
13+
# Initialize the spelling manager so it can perform deferred loading.
14+
@instance.locales = atom.config.get('spell-check.locales')
15+
@instance.localePaths = atom.config.get('spell-check.localePaths')
16+
@instance.useLocales = atom.config.get('spell-check.useLocales')
17+
18+
atom.config.onDidChange 'spell-check.locales', ({newValue, oldValue}) ->
19+
that.instance.locales = atom.config.get('spell-check.locales')
20+
that.instance.reloadLocales()
21+
that.updateViews()
22+
atom.config.onDidChange 'spell-check.localePaths', ({newValue, oldValue}) ->
23+
that.instance.localePaths = atom.config.get('spell-check.localePaths')
24+
that.instance.reloadLocales()
25+
that.updateViews()
26+
atom.config.onDidChange 'spell-check.useLocales', ({newValue, oldValue}) ->
27+
that.instance.useLocales = atom.config.get('spell-check.useLocales')
28+
that.instance.reloadLocales()
29+
that.updateViews()
30+
31+
# Add in the settings for known words checker.
32+
@instance.knownWords = atom.config.get('spell-check.knownWords')
33+
@instance.addKnownWords = atom.config.get('spell-check.addKnownWords')
34+
35+
atom.config.onDidChange 'spell-check.knownWords', ({newValue, oldValue}) ->
36+
that.instance.knownWords = atom.config.get('spell-check.knownWords')
37+
that.instance.reloadKnownWords()
38+
that.updateViews()
39+
atom.config.onDidChange 'spell-check.addKnownWords', ({newValue, oldValue}) ->
40+
that.instance.addKnownWords = atom.config.get('spell-check.addKnownWords')
41+
that.instance.reloadKnownWords()
42+
that.updateViews()
43+
44+
# Hook up the UI and processing.
645
@commandSubscription = atom.commands.add 'atom-workspace',
746
'spell-check:toggle': => @toggle()
847
@viewsByEditor = new WeakMap
948
@disposable = atom.workspace.observeTextEditors (editor) =>
1049
SpellCheckView ?= require './spell-check-view'
11-
spellCheckView = new SpellCheckView(editor)
50+
spellCheckView = new SpellCheckView(editor, @instance)
1251

1352
# save the {editor} into a map
1453
editorId = editor.id
@@ -17,14 +56,29 @@ module.exports =
1756
spellCheckViews[editorId]['active'] = true
1857
@viewsByEditor.set(editor, spellCheckView)
1958

20-
misspellingMarkersForEditor: (editor) ->
21-
@viewsByEditor.get(editor).markerLayer.getMarkers()
22-
2359
deactivate: ->
60+
@instance.deactivate()
61+
@instance = null
2462
@commandSubscription.dispose()
2563
@commandSubscription = null
2664
@disposable.dispose()
2765

66+
consumeSpellCheckers: (plugins) ->
67+
unless plugins instanceof Array
68+
plugins = [ plugins ]
69+
70+
for plugin in plugins
71+
@instance.addPluginChecker plugin
72+
73+
misspellingMarkersForEditor: (editor) ->
74+
@viewsByEditor.get(editor).markerLayer.getMarkers()
75+
76+
updateViews: ->
77+
for editorId of spellCheckViews
78+
view = spellCheckViews[editorId]
79+
if view['active']
80+
view['view'].updateMisspellings()
81+
2882
# Internal: Toggles the spell-check activation state.
2983
toggle: ->
3084
editorId = atom.workspace.getActiveTextEditor().id

lib/spell-check-handler.coffee

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,17 @@
1-
SpellChecker = require 'spellchecker'
1+
# Background task for checking the text of a buffer and returning the
2+
# spelling. Since this can be an expensive operation, it is intended to be run
3+
# in the background with the results returned asynchronously.
4+
backgroundCheck = (data) ->
5+
# Load a manager in memory and let it initialize.
6+
SpellCheckerManager = require './spell-check-manager.coffee'
7+
instance = SpellCheckerManager
8+
instance.locales = data.args.locales
9+
instance.localePaths = data.args.localePaths
10+
instance.useLocales = data.args.useLocales
11+
instance.knownWords = data.args.knownWords
12+
instance.addKnownWords = data.args.addKnownWords
213

3-
module.exports = ({id, text}) ->
4-
SpellChecker.add("GitHub")
5-
SpellChecker.add("github")
14+
misspellings = instance.check data.args, data.text
15+
{id: data.args.id, misspellings}
616

7-
misspelledCharacterRanges = SpellChecker.checkSpelling(text)
8-
9-
row = 0
10-
rangeIndex = 0
11-
characterIndex = 0
12-
misspellings = []
13-
while characterIndex < text.length and rangeIndex < misspelledCharacterRanges.length
14-
lineBreakIndex = text.indexOf('\n', characterIndex)
15-
if lineBreakIndex is -1
16-
lineBreakIndex = Infinity
17-
18-
loop
19-
range = misspelledCharacterRanges[rangeIndex]
20-
if range and range.start < lineBreakIndex
21-
misspellings.push([
22-
[row, range.start - characterIndex],
23-
[row, range.end - characterIndex]
24-
])
25-
rangeIndex++
26-
else
27-
break
28-
29-
characterIndex = lineBreakIndex + 1
30-
row++
31-
32-
{id, misspellings}
17+
module.exports = backgroundCheck

0 commit comments

Comments
 (0)