Merge pull request #229 from sharelatex/spelling-cache

add client-side spelling cache to reduce load on server
This commit is contained in:
Brian Gough 2016-03-03 16:33:04 +00:00
commit f2d62e0653
3 changed files with 63 additions and 14 deletions

View file

@ -20,6 +20,7 @@ div.full-size(
keybindings="settings.mode", keybindings="settings.mode",
font-size="settings.fontSize", font-size="settings.fontSize",
auto-complete="settings.autoComplete", auto-complete="settings.autoComplete",
spell-check="true",
spell-check-language="project.spellCheckLanguage", spell-check-language="project.spellCheckLanguage",
highlights="onlineUserCursorHighlights[editor.open_doc_id]" highlights="onlineUserCursorHighlights[editor.open_doc_id]"
show-print-margin="false", show-print-margin="false",

View file

@ -18,7 +18,7 @@ define [
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}" url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
return url return url
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage) -> App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory) ->
monkeyPatchSearch($rootScope, $compile) monkeyPatchSearch($rootScope, $compile)
return { return {
@ -29,6 +29,7 @@ define [
fontSize: "=" fontSize: "="
autoComplete: "=" autoComplete: "="
sharejsDoc: "=" sharejsDoc: "="
spellCheck: "="
spellCheckLanguage: "=" spellCheckLanguage: "="
highlights: "=" highlights: "="
text: "=" text: "="
@ -55,7 +56,9 @@ define [
scope.name = attrs.aceEditor scope.name = attrs.aceEditor
autoCompleteManager = new AutoCompleteManager(scope, editor, element) autoCompleteManager = new AutoCompleteManager(scope, editor, element)
spellCheckManager = new SpellCheckManager(scope, editor, element) if scope.spellCheck # only enable spellcheck when explicitly required
spellCheckCache = $cacheFactory("spellCheck-#{scope.name}", {capacity: 1000})
spellCheckManager = new SpellCheckManager(scope, editor, element, spellCheckCache)
undoManager = new UndoManager(scope, editor, element) undoManager = new UndoManager(scope, editor, element)
highlightsManager = new HighlightsManager(scope, editor, element) highlightsManager = new HighlightsManager(scope, editor, element)
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage) cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)

View file

@ -5,7 +5,7 @@ define [
Range = ace.require("ace/range").Range Range = ace.require("ace/range").Range
class SpellCheckManager class SpellCheckManager
constructor: (@$scope, @editor, @element) -> constructor: (@$scope, @editor, @element, @cache) ->
$(document.body).append @element.find(".spell-check-menu") $(document.body).append @element.find(".spell-check-menu")
@updatedLines = [] @updatedLines = []
@ -102,6 +102,8 @@ define [
learnWord: (highlight) -> learnWord: (highlight) ->
@apiRequest "/learn", word: highlight.word @apiRequest "/learn", word: highlight.word
@highlightedWordManager.removeWord highlight.word @highlightedWordManager.removeWord highlight.word
language = @$scope.spellCheckLanguage
@cache?.put("#{language}:#{highlight.word}", true)
getHighlightedWordAtCursor: () -> getHighlightedWordAtCursor: () ->
cursor = @editor.getCursorPosition() cursor = @editor.getCursorPosition()
@ -143,24 +145,67 @@ define [
runSpellCheck: (linesToProcess) -> runSpellCheck: (linesToProcess) ->
{words, positions} = @getWords(linesToProcess) {words, positions} = @getWords(linesToProcess)
language = @$scope.spellCheckLanguage language = @$scope.spellCheckLanguage
@apiRequest "/check", {language: language, words: words}, (error, result) =>
if error? or !result? or !result.misspellings?
return null
highlights = []
seen = {}
newWords = []
newPositions = []
# iterate through all words, building up a list of
# newWords/newPositions not in the cache
for word, i in words
key = "#{language}:#{word}"
seen[key] ?= @cache.get(key) # avoid hitting the cache unnecessarily
cached = seen[key]
if not cached?
newWords.push words[i]
newPositions.push positions[i]
else if cached is true
# word is correct
else
highlights.push
column: positions[i].column
row: positions[i].row
word: word
suggestions: cached
words = newWords
positions = newPositions
displayResult = (highlights) =>
if linesToProcess? if linesToProcess?
for shouldProcess, row in linesToProcess for shouldProcess, row in linesToProcess
@highlightedWordManager.clearRows(row, row) if shouldProcess @highlightedWordManager.clearRows(row, row) if shouldProcess
else else
@highlightedWordManager.clearRows() @highlightedWordManager.clearRows()
for highlight in highlights
@highlightedWordManager.addHighlight highlight
if not words.length
displayResult highlights
else
@apiRequest "/check", {language: language, words: words}, (error, result) =>
if error? or !result? or !result.misspellings?
return null
mispelled = []
for misspelling in result.misspellings for misspelling in result.misspellings
word = words[misspelling.index] word = words[misspelling.index]
position = positions[misspelling.index] position = positions[misspelling.index]
@highlightedWordManager.addHighlight mispelled[misspelling.index] = true
highlights.push
column: position.column column: position.column
row: position.row row: position.row
word: word word: word
suggestions: misspelling.suggestions suggestions: misspelling.suggestions
key = "#{language}:#{word}"
if not seen[key]
@cache.put key, misspelling.suggestions
seen[key] = true
for word, i in words when not mispelled[i]
key = "#{language}:#{word}"
if not seen[key]
@cache.put(key, true)
seen[key] = true
displayResult highlights
getWords: (linesToProcess) -> getWords: (linesToProcess) ->
lines = @editor.getValue().split("\n") lines = @editor.getValue().split("\n")