From b106c48956db0d1164d341ef47a07a083bd1d337 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 4 Jan 2016 16:38:48 -0800 Subject: [PATCH 1/7] Add benchmark script --- script/benchmark.coffee | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100755 script/benchmark.coffee diff --git a/script/benchmark.coffee b/script/benchmark.coffee new file mode 100755 index 00000000..f16fe537 --- /dev/null +++ b/script/benchmark.coffee @@ -0,0 +1,15 @@ +#!/usr/bin/env coffee + +handler = require '../lib/spell-check-handler' +fs = require 'fs' + +pathToCheck = process.argv[2] +console.log("Spellchecking %s...", pathToCheck) + +text = fs.readFileSync(pathToCheck, 'utf8') + +t0 = Date.now() +result = handler({id: 1, text}) +t1 = Date.now() + +console.log("Found %d misspellings in %d milliseconds", result.misspellings.length, t1 - t0) From 706bc262dec5b524699ff64ee5df89862edee583 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 4 Jan 2016 16:40:49 -0800 Subject: [PATCH 2/7] Use new bulk spell-checking method in task handler --- lib/spell-check-handler.coffee | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/spell-check-handler.coffee b/lib/spell-check-handler.coffee index 2d64aa3c..8028ed1f 100644 --- a/lib/spell-check-handler.coffee +++ b/lib/spell-check-handler.coffee @@ -1,18 +1,29 @@ SpellChecker = require 'spellchecker' -wordRegex = /(?:^|[\s\[\]"'])([a-zA-Z]+([a-zA-Z']+[a-zA-Z])?)(?=[\s\.\[\]:,"']|$)/g - module.exports = ({id, text}) -> + misspelledCharacterRanges = SpellChecker.checkSpelling(text) + row = 0 + rangeIndex = 0 + characterIndex = 0 misspellings = [] - for line in text.split('\n') - while matches = wordRegex.exec(line) - word = matches[1] - continue if word in ['GitHub', 'github'] - continue unless SpellChecker.isMisspelled(word) + while characterIndex < text.length and rangeIndex < misspelledCharacterRanges.length + lineBreakIndex = text.indexOf('\n', characterIndex) + if lineBreakIndex is -1 + lineBreakIndex = Infinity - startColumn = matches.index + matches[0].length - word.length - endColumn = startColumn + word.length - misspellings.push([[row, startColumn], [row, endColumn]]) + loop + range = misspelledCharacterRanges[rangeIndex] + if range and range.start < lineBreakIndex + misspellings.push([ + [row, range.start - characterIndex], + [row, range.end - characterIndex] + ]) + rangeIndex++ + else + break + + characterIndex = lineBreakIndex + 1 row++ + {id, misspellings} From b53c6bc0b3cc19f205f5505fc94ea7d81cf90ae8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 7 Jan 2016 13:25:45 -0800 Subject: [PATCH 3/7] Use multi-line strings in spec --- spec/spell-check-spec.coffee | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/spec/spell-check-spec.coffee b/spec/spell-check-spec.coffee index ad367385..0fbdee85 100644 --- a/spec/spell-check-spec.coffee +++ b/spec/spell-check-spec.coffee @@ -25,7 +25,7 @@ describe "Spell check", -> editorElement = atom.views.getView(editor) it "decorates all misspelled words", -> - editor.setText("This middle of thiss sentencts has issues and the \"edn\" 'dsoe' too") + editor.setText("This middle of thiss\nsentencts\n\nhas issues and the \"edn\" 'dsoe' too") atom.config.set('spell-check.grammars', ['source.js']) decorations = null @@ -34,12 +34,15 @@ describe "Spell check", -> decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling') decorations.length > 0 + textForDecoration = ({marker}) -> + editor.getTextInBufferRange(marker.getBufferRange()) + runs -> expect(decorations.length).toBe 4 - expect(decorations[0].marker.getBufferRange()).toEqual [[0, 15], [0, 20]] - expect(decorations[1].marker.getBufferRange()).toEqual [[0, 21], [0, 30]] - expect(decorations[2].marker.getBufferRange()).toEqual [[0, 51], [0, 54]] - expect(decorations[3].marker.getBufferRange()).toEqual [[0, 57], [0, 61]] + expect(textForDecoration(decorations[0])).toEqual "thiss" + expect(textForDecoration(decorations[1])).toEqual "sentencts" + expect(textForDecoration(decorations[2])).toEqual "edn" + expect(textForDecoration(decorations[3])).toEqual "dsoe" it "hides decorations when a misspelled word is edited", -> editor.setText('notaword') From 492fb3b7119ad8e695bed7fb240547cfcf83d025 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 7 Jan 2016 13:32:19 -0800 Subject: [PATCH 4/7] Don't mark github as a spelling error --- lib/spell-check-handler.coffee | 3 +++ spec/spell-check-spec.coffee | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/spell-check-handler.coffee b/lib/spell-check-handler.coffee index 8028ed1f..b44054d1 100644 --- a/lib/spell-check-handler.coffee +++ b/lib/spell-check-handler.coffee @@ -1,6 +1,9 @@ SpellChecker = require 'spellchecker' module.exports = ({id, text}) -> + SpellChecker.add("GitHub") + SpellChecker.add("github") + misspelledCharacterRanges = SpellChecker.checkSpelling(text) row = 0 diff --git a/spec/spell-check-spec.coffee b/spec/spell-check-spec.coffee index 0fbdee85..3f3146d5 100644 --- a/spec/spell-check-spec.coffee +++ b/spec/spell-check-spec.coffee @@ -1,6 +1,9 @@ describe "Spell check", -> [workspaceElement, editor, editorElement] = [] + textForDecoration = ({marker}) -> + editor.getTextInBufferRange(marker.getBufferRange()) + beforeEach -> workspaceElement = atom.views.getView(atom.workspace) @@ -34,8 +37,6 @@ describe "Spell check", -> decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling') decorations.length > 0 - textForDecoration = ({marker}) -> - editor.getTextInBufferRange(marker.getBufferRange()) runs -> expect(decorations.length).toBe 4 @@ -44,6 +45,20 @@ describe "Spell check", -> expect(textForDecoration(decorations[2])).toEqual "edn" expect(textForDecoration(decorations[3])).toEqual "dsoe" + it "doesn't consider our company's name to be a spelling error", -> + editor.setText("GitHub (aka github): Where codez are built.") + atom.config.set('spell-check.grammars', ['source.js']) + + decorations = null + + waitsFor -> + decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling') + decorations.length > 0 + + runs -> + expect(decorations.length).toBe 1 + expect(textForDecoration(decorations[0])).toBe "codez" + it "hides decorations when a misspelled word is edited", -> editor.setText('notaword') advanceClock(editor.getBuffer().getStoppedChangingDelay()) From e12d1c2aef9baf4cf1f4a1196cec5fd089882756 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 8 Jan 2016 10:47:06 -0800 Subject: [PATCH 5/7] Put misspelling decorations in their owm marker layer --- lib/corrections-view.coffee | 2 +- lib/main.coffee | 12 ++--- lib/misspelling-view.coffee | 44 ------------------ lib/spell-check-view.coffee | 52 +++++++++++++++++----- spec/spell-check-spec.coffee | 86 +++++++++++++++++++----------------- 5 files changed, 95 insertions(+), 101 deletions(-) delete mode 100644 lib/misspelling-view.coffee diff --git a/lib/corrections-view.coffee b/lib/corrections-view.coffee index 8e5a30cb..5f7c29b7 100644 --- a/lib/corrections-view.coffee +++ b/lib/corrections-view.coffee @@ -24,7 +24,7 @@ class CorrectionsView extends SelectListView @cancel() return unless correction @editor.transact => - @editor.selectMarker(@marker) + @editor.setSelectedBufferRange(@marker.getRange()) @editor.insertText(correction) cancelled: -> diff --git a/lib/main.coffee b/lib/main.coffee index 29c6f302..a8d484a1 100644 --- a/lib/main.coffee +++ b/lib/main.coffee @@ -14,11 +14,13 @@ module.exports = description: 'List of scopes for languages which will be checked for misspellings. See [the README](https://github.com/atom/spell-check#spell-check-package-) for more information on finding the correct scope for a specific language.' activate: -> - @disposable = atom.workspace.observeTextEditors(addViewToEditor) + @viewsByEditor = new WeakMap + @disposable = atom.workspace.observeTextEditors (editor) => + SpellCheckView ?= require './spell-check-view' + @viewsByEditor.set(editor, new SpellCheckView(editor)) + + misspellingMarkersForEditor: (editor) -> + @viewsByEditor.get(editor).markerLayer.getMarkers() deactivate: -> @disposable.dispose() - -addViewToEditor = (editor) -> - SpellCheckView ?= require './spell-check-view' - new SpellCheckView(editor) diff --git a/lib/misspelling-view.coffee b/lib/misspelling-view.coffee deleted file mode 100644 index 9d354c2b..00000000 --- a/lib/misspelling-view.coffee +++ /dev/null @@ -1,44 +0,0 @@ -CorrectionsView = null - -module.exports = -class MisspellingView - constructor: (bufferRange, @editor) -> - @createMarker(bufferRange) - @correctMispellingCommand = atom.commands.add atom.views.getView(@editor), 'spell-check:correct-misspelling', => - if @containsCursor() - CorrectionsView ?= require './corrections-view' - @correctionsView?.destroy() - @correctionsView = new CorrectionsView(@editor, @getCorrections(), @marker) - - createMarker: (bufferRange) -> - @marker = @editor.markBufferRange(bufferRange, - invalidate: 'touch', - replicate: false, - persistent: false, - maintainHistory: false - ) - @editor.decorateMarker(@marker, - type: 'highlight', - class: 'spell-check-misspelling', - deprecatedRegionClass: 'misspelling' - ) - - getCorrections: -> - screenRange = @marker.getScreenRange() - misspelling = @editor.getTextInRange(@editor.bufferRangeForScreenRange(screenRange)) - SpellChecker = require 'spellchecker' - corrections = SpellChecker.getCorrectionsForMisspelling(misspelling) - - containsCursor: -> - cursor = @editor.getCursorScreenPosition() - @marker.getScreenRange().containsPoint(cursor, false) - - destroy: -> - @correctMispellingCommand?.dispose() - @correctMispellingCommand = null - - @correctionsView?.remove() - @correctionsView = null - - @marker?.destroy() - @marker = null diff --git a/lib/spell-check-view.coffee b/lib/spell-check-view.coffee index b6a882e6..9663db3d 100644 --- a/lib/spell-check-view.coffee +++ b/lib/spell-check-view.coffee @@ -1,8 +1,10 @@ _ = require 'underscore-plus' {CompositeDisposable} = require 'atom' -MisspellingView = require './misspelling-view' SpellCheckTask = require './spell-check-task' +CorrectionsView = null +SpellChecker = null + module.exports = class SpellCheckView @content: -> @@ -10,12 +12,18 @@ class SpellCheckView constructor: (@editor) -> @disposables = new CompositeDisposable - @views = [] @task = new SpellCheckTask() + @initializeMarkerLayer() + + @correctMisspellingCommand = atom.commands.add atom.views.getView(@editor), 'spell-check:correct-misspelling', => + if marker = @markerLayer.findMarkers({containsPoint: @editor.getCursorBufferPosition()})[0] + CorrectionsView ?= require './corrections-view' + @correctionsView?.destroy() + @correctionsView = new CorrectionsView(@editor, @getCorrections(marker), marker) @task.onDidSpellCheck (misspellings) => - @destroyViews() - @addViews(misspellings) if @buffer? + @detroyMarkers() + @addMarkers(misspellings) if @buffer? @disposables.add @editor.onDidChangePath => @subscribeToBuffer() @@ -33,13 +41,25 @@ class SpellCheckView @disposables.add @editor.onDidDestroy(@destroy.bind(this)) + initializeMarkerLayer: -> + @markerLayer = @editor.getBuffer().addMarkerLayer() + @markerLayerDecoration = @editor.decorateMarkerLayer(@markerLayer, { + type: 'highlight', + class: 'spell-check-misspelling', + deprecatedRegionClass: 'misspelling' + }) + destroy: -> @unsubscribeFromBuffer() @disposables.dispose() @task.terminate() + @markerLayer.destroy() + @markerLayerDecoration.destroy() + @correctMisspellingCommand.dispose() + @correctionsView?.remove() unsubscribeFromBuffer: -> - @destroyViews() + @detroyMarkers() if @buffer? @bufferDisposable.dispose() @@ -57,14 +77,19 @@ class SpellCheckView grammar = @editor.getGrammar().scopeName _.contains(atom.config.get('spell-check.grammars'), grammar) - destroyViews: -> - while view = @views.shift() - view.destroy() + detroyMarkers: -> + @markerLayer.destroy() + @markerLayerDecoration.destroy() + @initializeMarkerLayer() - addViews: (misspellings) -> + addMarkers: (misspellings) -> for misspelling in misspellings - view = new MisspellingView(misspelling, @editor) - @views.push(view) + @markerLayer.markRange(misspelling, + invalidate: 'touch', + replicate: 'false', + persistent: false, + maintainHistory: false, + ) updateMisspellings: -> # Task::start can throw errors atom/atom#3326 @@ -72,3 +97,8 @@ class SpellCheckView @task.start(@buffer.getText()) catch error console.warn('Error starting spell check task', error.stack ? error) + + getCorrections: (marker) -> + SpellChecker ?= require 'spellchecker' + misspelling = @editor.getTextInBufferRange(marker.getRange()) + corrections = SpellChecker.getCorrectionsForMisspelling(misspelling) diff --git a/spec/spell-check-spec.coffee b/spec/spell-check-spec.coffee index 3f3146d5..0ce215e7 100644 --- a/spec/spell-check-spec.coffee +++ b/spec/spell-check-spec.coffee @@ -1,8 +1,11 @@ describe "Spell check", -> - [workspaceElement, editor, editorElement] = [] + [workspaceElement, editor, editorElement, spellCheckModule] = [] - textForDecoration = ({marker}) -> - editor.getTextInBufferRange(marker.getBufferRange()) + textForMarker = (marker) -> + editor.getTextInBufferRange(marker.getRange()) + + getMisspellingMarkers = -> + spellCheckModule.misspellingMarkersForEditor(editor) beforeEach -> workspaceElement = atom.views.getView(atom.workspace) @@ -20,7 +23,8 @@ describe "Spell check", -> atom.workspace.open('sample.js') waitsForPromise -> - atom.packages.activatePackage('spell-check') + atom.packages.activatePackage('spell-check').then ({mainModule}) -> + spellCheckModule = mainModule runs -> jasmine.attachToDOM(workspaceElement) @@ -31,52 +35,51 @@ describe "Spell check", -> editor.setText("This middle of thiss\nsentencts\n\nhas issues and the \"edn\" 'dsoe' too") atom.config.set('spell-check.grammars', ['source.js']) - decorations = null - + misspellingMarkers = null waitsFor -> - decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling') - decorations.length > 0 - + misspellingMarkers = getMisspellingMarkers() + misspellingMarkers.length > 0 runs -> - expect(decorations.length).toBe 4 - expect(textForDecoration(decorations[0])).toEqual "thiss" - expect(textForDecoration(decorations[1])).toEqual "sentencts" - expect(textForDecoration(decorations[2])).toEqual "edn" - expect(textForDecoration(decorations[3])).toEqual "dsoe" + expect(misspellingMarkers.length).toBe 4 + expect(textForMarker(misspellingMarkers[0])).toEqual "thiss" + expect(textForMarker(misspellingMarkers[1])).toEqual "sentencts" + expect(textForMarker(misspellingMarkers[2])).toEqual "edn" + expect(textForMarker(misspellingMarkers[3])).toEqual "dsoe" it "doesn't consider our company's name to be a spelling error", -> editor.setText("GitHub (aka github): Where codez are built.") atom.config.set('spell-check.grammars', ['source.js']) - decorations = null - + misspellingMarkers = null waitsFor -> - decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling') - decorations.length > 0 + misspellingMarkers = getMisspellingMarkers() + misspellingMarkers.length > 0 runs -> - expect(decorations.length).toBe 1 - expect(textForDecoration(decorations[0])).toBe "codez" + expect(misspellingMarkers.length).toBe 1 + expect(textForMarker(misspellingMarkers[0])).toBe "codez" it "hides decorations when a misspelled word is edited", -> editor.setText('notaword') advanceClock(editor.getBuffer().getStoppedChangingDelay()) atom.config.set('spell-check.grammars', ['source.js']) - decorations = null + misspellingMarkers = null waitsFor -> - decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling') - decorations.length > 0 + misspellingMarkers = getMisspellingMarkers() + misspellingMarkers.length > 0 runs -> - expect(decorations.length).toBe 1 + expect(misspellingMarkers.length).toBe 1 editor.moveToEndOfLine() editor.insertText('a') advanceClock(editor.getBuffer().getStoppedChangingDelay()) - decorations = editor.getHighlightDecorations(class: 'spell-check-misspelling') - expect(decorations.length).toBe 1 - expect(decorations[0].marker.isValid()).toBe false + + misspellingMarkers = getMisspellingMarkers() + + expect(misspellingMarkers.length).toBe 1 + expect(misspellingMarkers[0].isValid()).toBe false describe "when spell checking for a grammar is removed", -> it "removes all the misspellings", -> @@ -84,14 +87,15 @@ describe "Spell check", -> advanceClock(editor.getBuffer().getStoppedChangingDelay()) atom.config.set('spell-check.grammars', ['source.js']) - decorations = null + misspellingMarkers = null waitsFor -> - editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0 + misspellingMarkers = getMisspellingMarkers() + misspellingMarkers.length > 0 runs -> - expect(editor.getHighlightDecorations(class: 'spell-check-misspelling').length).toBe 1 + expect(getMisspellingMarkers().length).toBe 1 atom.config.set('spell-check.grammars', []) - expect(editor.getHighlightDecorations(class: 'spell-check-misspelling').length).toBe 0 + expect(getMisspellingMarkers().length).toBe 0 describe "when the editor's grammar changes to one that does not have spell check enabled", -> it "removes all the misspellings", -> @@ -100,12 +104,12 @@ describe "Spell check", -> atom.config.set('spell-check.grammars', ['source.js']) waitsFor -> - editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0 + getMisspellingMarkers().length > 0 runs -> - expect(editor.getHighlightDecorations(class: 'spell-check-misspelling').length).toBe 1 + expect(getMisspellingMarkers().length).toBe 1 editor.setGrammar(atom.grammars.selectGrammar('.txt')) - expect(editor.getHighlightDecorations(class: 'spell-check-misspelling').length).toBe 0 + expect(getMisspellingMarkers().length).toBe 0 describe "when 'spell-check:correct-misspelling' is triggered on the editor", -> describe "when the cursor touches a misspelling that has corrections", -> @@ -115,9 +119,11 @@ describe "Spell check", -> atom.config.set('spell-check.grammars', ['source.js']) waitsFor -> - editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0 + getMisspellingMarkers().length is 1 runs -> + expect(getMisspellingMarkers()[0].isValid()).toBe true + atom.commands.dispatch editorElement, 'spell-check:correct-misspelling' correctionsElement = editorElement.querySelector('.corrections') @@ -129,8 +135,8 @@ describe "Spell check", -> expect(editor.getText()).toBe 'together' expect(editor.getCursorBufferPosition()).toEqual [0, 8] - advanceClock(editor.getBuffer().getStoppedChangingDelay()) - expect(editorElement.querySelectorAll('.spell-check-misspelling').length).toBe 0 + + expect(getMisspellingMarkers()[0].isValid()).toBe false expect(editorElement.querySelector('.corrections')).toBeNull() describe "when the cursor touches a misspelling that has no corrections", -> @@ -140,7 +146,7 @@ describe "Spell check", -> atom.config.set('spell-check.grammars', ['source.js']) waitsFor -> - editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0 + getMisspellingMarkers().length > 0 runs -> atom.commands.dispatch editorElement, 'spell-check:correct-misspelling' @@ -154,8 +160,8 @@ describe "Spell check", -> atom.config.set('spell-check.grammars', ['source.js']) waitsFor -> - editor.getHighlightDecorations(class: 'spell-check-misspelling').length > 0 + getMisspellingMarkers().length > 0 runs -> editor.destroy() - expect(editor.getMarkers().length).toBe 0 + expect(getMisspellingMarkers().length).toBe 0 From 7bee02459fa6ddad21ef6b667082e029e875721e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 8 Jan 2016 10:53:40 -0800 Subject: [PATCH 6/7] :arrow_up: spellchecker (pre-release) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9a94f7bb..30e156b4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Highlights misspelled words and shows possible corrections.", "dependencies": { "atom-space-pen-views": "^2.0.0", - "spellchecker": "^3.1.2", + "spellchecker": "3.2.0-0", "underscore-plus": "^1" }, "repository": "https://github.com/atom/spell-check", From 577ff803147e9f8eb93a5fc38ed5ec76aa300005 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 11 Jan 2016 15:34:45 -0800 Subject: [PATCH 7/7] :arrow_up: spellchecker --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30e156b4..b064cbdf 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Highlights misspelled words and shows possible corrections.", "dependencies": { "atom-space-pen-views": "^2.0.0", - "spellchecker": "3.2.0-0", + "spellchecker": "3.2.0", "underscore-plus": "^1" }, "repository": "https://github.com/atom/spell-check",