From 37ca7b54a69186c0ed0bffd75ebfc467f83eff04 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Tue, 24 Apr 2018 10:27:17 +0100 Subject: [PATCH 01/17] Re-implement spell check manager with adapter to abstract away editor --- .../spell-check/SpellCheckManager.coffee | 184 +++++------------- 1 file changed, 51 insertions(+), 133 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index acbd636531..4dee1faf52 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -1,129 +1,38 @@ -define [ - "ide/editor/directives/aceEditor/spell-check/HighlightedWordManager" - "ace/ace" -], (HighlightedWordManager) -> - Range = ace.require("ace/range").Range - +define [], () -> class SpellCheckManager - constructor: (@$scope, @editor, @element, @cache, @$http, @$q) -> - $(document.body).append @element.find(".spell-check-menu") + constructor: (@$scope, @cache, @$http, @$q, @adapter) -> @inProgressRequest = null @updatedLines = [] - @highlightedWordManager = new HighlightedWordManager(@editor) - @$scope.$watch "spellCheckLanguage", (language, oldLanguage) => + @$scope.$watch 'spellCheckLanguage', (language, oldLanguage) => if language != oldLanguage and oldLanguage? @runFullCheck() - onChange = (e) => - @runCheckOnChange(e) - - onScroll = () => - @closeContextMenu() + init: () -> + @updatedLines = Array(@adapter.getLines().length).fill(true) + @runSpellCheckSoon(200) if @isSpellCheckEnabled() - @editor.on "changeSession", (e) => - @highlightedWordManager.reset() - if @inProgressRequest? - @inProgressRequest.abort() + isSpellCheckEnabled: () -> + return !!( + @$scope.spellCheck and + @$scope.spellCheckLanguage and + @$scope.spellCheckLanguage != '' + ) - if @$scope.spellCheckEnabled and @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != "" - @runSpellCheckSoon(200) - - e.oldSession?.getDocument().off "change", onChange - e.session.getDocument().on "change", onChange - - e.oldSession?.off "changeScrollTop", onScroll - e.session.on "changeScrollTop", onScroll - - @$scope.spellingMenu = {left: '0px', top: '0px'} - - @editor.on "nativecontextmenu", (e) => - e.domEvent.stopPropagation(); - @closeContextMenu(e.domEvent) - @openContextMenu(e.domEvent) - - $(document).on "click", (e) => - if e.which != 3 # Ignore if this was a right click - @closeContextMenu(e) - return true - - @$scope.replaceWord = (highlight, suggestion) => - @replaceWord(highlight, suggestion) - - @$scope.learnWord = (highlight) => - @learnWord(highlight) - - runFullCheck: () -> - @highlightedWordManager.clearRows() - if @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != "" - @runSpellCheck() - - runCheckOnChange: (e) -> - if @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != "" - @highlightedWordManager.applyChange(e) - @markLinesAsUpdated(e) + onChange: (e) => + if @isSpellCheckEnabled() + @markLinesAsUpdated(@adapter.normalizeChangeEvent(e)) @runSpellCheckSoon() - openContextMenu: (e) -> - position = @editor.renderer.screenToTextCoordinates(e.clientX, e.clientY) - highlight = @highlightedWordManager.findHighlightWithinRange - start: position - end: position + onSessionChange: () => + @adapter.wordManager.reset() + @inProgressRequest.abort() if @inProgressRequest? - @$scope.$apply () => - @$scope.spellingMenu.highlight = highlight + @runSpellCheckSoon(200) if @isSpellCheckEnabled() - if highlight - e.stopPropagation() - e.preventDefault() - - @editor.getSession().getSelection().setSelectionRange( - new Range( - highlight.row, highlight.column - highlight.row, highlight.column + highlight.word.length - ) - ) - - @$scope.$apply () => - @$scope.spellingMenu.open = true - @$scope.spellingMenu.left = e.clientX + 'px' - @$scope.spellingMenu.top = e.clientY + 'px' - return false - - closeContextMenu: (e) -> - # this is triggered on scroll, so for performance only apply - # setting when it changes - if @$scope?.spellingMenu?.open != false - @$scope.$apply () => - @$scope.spellingMenu.open = false - - replaceWord: (highlight, text) -> - @editor.getSession().replace(new Range( - highlight.row, highlight.column, - highlight.row, highlight.column + highlight.word.length - ), text) - - learnWord: (highlight) -> - @apiRequest "/learn", word: highlight.word - @highlightedWordManager.removeWord highlight.word - language = @$scope.spellCheckLanguage - @cache?.put("#{language}:#{highlight.word}", true) - - getHighlightedWordAtCursor: () -> - cursor = @editor.getCursorPosition() - highlight = @highlightedWordManager.findHighlightWithinRange - start: cursor - end: cursor - return highlight - - runSpellCheckSoon: (delay = 1000) -> - run = () => - delete @timeoutId - @runSpellCheck(@updatedLines) - @updatedLines = [] - if @timeoutId? - clearTimeout @timeoutId - @timeoutId = setTimeout run, delay + runFullCheck: () -> + @adapter.wordManager.reset() + @runSpellCheck() if @isSpellCheckEnabled() markLinesAsUpdated: (change) -> start = change.start @@ -146,6 +55,15 @@ define [ @updatedLines[start.row] = true removeLines() + runSpellCheckSoon: (delay = 1000) -> + run = () => + delete @timeoutId + @runSpellCheck(@updatedLines) + @updatedLines = [] + if @timeoutId? + clearTimeout @timeoutId + @timeoutId = setTimeout run, delay + runSpellCheck: (linesToProcess) -> {words, positions} = @getWords(linesToProcess) language = @$scope.spellCheckLanguage @@ -178,11 +96,11 @@ define [ displayResult = (highlights) => if linesToProcess? for shouldProcess, row in linesToProcess - @highlightedWordManager.clearRows(row, row) if shouldProcess + @adapter.wordManager.clearRow(row) if shouldProcess else - @highlightedWordManager.clearRows() + @adapter.wordManager.reset() for highlight in highlights - @highlightedWordManager.addHighlight highlight + @adapter.wordManager.addHighlight highlight if not words.length displayResult highlights @@ -212,8 +130,24 @@ define [ seen[key] = true displayResult highlights + apiRequest: (endpoint, data, callback = (error, result) ->)-> + data.token = window.user.id + data._csrf = window.csrfToken + # use angular timeout option to cancel request if doc is changed + requestHandler = @$q.defer() + options = {timeout: requestHandler.promise} + httpRequest = @$http.post("/spelling" + endpoint, data, options) + .then (response) => + callback(null, response.data) + .catch (response) => + callback(new Error('api failure')) + # provide a method to cancel the request + abortRequest = () -> + requestHandler.resolve() + return { abort: abortRequest } + getWords: (linesToProcess) -> - lines = @editor.getValue().split("\n") + lines = @adapter.getLines() words = [] positions = [] for line, row in lines @@ -232,22 +166,6 @@ define [ words.push(word) return words: words, positions: positions - apiRequest: (endpoint, data, callback = (error, result) ->)-> - data.token = window.user.id - data._csrf = window.csrfToken - # use angular timeout option to cancel request if doc is changed - requestHandler = @$q.defer() - options = {timeout: requestHandler.promise} - httpRequest = @$http.post("/spelling" + endpoint, data, options) - .then (response) => - callback(null, response.data) - .catch (response) => - callback(new Error('api failure')) - # provide a method to cancel the request - abortRequest = () -> - requestHandler.resolve() - return { abort: abortRequest } - blacklistedCommandRegex: /// \\ # initial backslash (label # any of these commands From 22e41cdce75c02af3ce87ea29fe24629eef7bce5 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Tue, 24 Apr 2018 10:27:17 +0100 Subject: [PATCH 02/17] Simplify word manager to use Range + Anchor to automatically keep marker positions up-to-date Re-implement highlighted word manager to be simpler --- .../spell-check/HighlightedWordManager.coffee | 138 +++--------------- 1 file changed, 22 insertions(+), 116 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee index 5014559562..ba7561411d 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee @@ -13,18 +13,31 @@ define [ class HighlightedWordManager constructor: (@editor) -> @reset() - + reset: () -> + @highlights?.rows.forEach (highlight) => + @editor.getSession().removeMarker(highlight.markerId) @highlights = rows: [] addHighlight: (highlight) -> unless highlight instanceof Highlight highlight = new Highlight(highlight) - range = new Range( - highlight.row, highlight.column, - highlight.row, highlight.column + highlight.word.length - ) - highlight.markerId = @editor.getSession().addMarker range, "spelling-highlight", 'text', false + + session = @editor.getSession() + doc = session.getDocument() + # Set up Range that will automatically update it's positions when the + # document changes + range = new Range() + range.start = doc.createAnchor({ + row: highlight.row, + column: highlight.column + }) + range.end = doc.createAnchor({ + row: highlight.row, + column: highlight.column + highlight.word.length + }) + + highlight.markerId = session.addMarker range, "spelling-highlight", 'text', false @highlights.rows[highlight.row] ||= [] @highlights.rows[highlight.row].push highlight @@ -34,114 +47,7 @@ define [ if h == highlight @highlights.rows[highlight.row].splice(i, 1) - removeWord: (word) -> - toRemove = [] - for row in @highlights.rows - for highlight in (row || []) - if highlight.word == word - toRemove.push(highlight) - for highlight in toRemove + clearRow: (row) -> + row = @highlights.rows[row] + for highlight in (row || []).slice() @removeHighlight highlight - - moveHighlight: (highlight, position) -> - @removeHighlight highlight - highlight.row = position.row - highlight.column = position.column - @addHighlight highlight - - clearRows: (from, to) -> - from ||= 0 - to ||= @highlights.rows.length - 1 - for row in @highlights.rows.slice(from, to + 1) - for highlight in (row || []).slice(0) - @removeHighlight highlight - - insertRows: (offset, number) -> - # rows are inserted after offset. i.e. offset row is not modified - affectedHighlights = [] - for row in @highlights.rows.slice(offset) - affectedHighlights.push(highlight) for highlight in (row || []) - for highlight in affectedHighlights - @moveHighlight highlight, - row: highlight.row + number - column: highlight.column - - removeRows: (offset, number) -> - # offset is the first row to delete - affectedHighlights = [] - for row in @highlights.rows.slice(offset) - affectedHighlights.push(highlight) for highlight in (row || []) - for highlight in affectedHighlights - if highlight.row >= offset + number - @moveHighlight highlight, - row: highlight.row - number - column: highlight.column - else - @removeHighlight highlight - - findHighlightWithinRange: (range) -> - rows = @highlights.rows.slice(range.start.row, range.end.row + 1) - for row in rows - for highlight in (row || []) - if @_doesHighlightOverlapRange(highlight, range.start, range.end) - return highlight - return null - - applyChange: (change) -> - start = change.start - end = change.end - if change.action == "insert" - if start.row != end.row - rowsAdded = end.row - start.row - @insertRows start.row + 1, rowsAdded - # make a copy since we're going to modify in place - oldHighlights = (@highlights.rows[start.row] || []).slice(0) - for highlight in oldHighlights - if highlight.column > start.column - # insertion was fully before this highlight - @moveHighlight highlight, - row: end.row - column: highlight.column + (end.column - start.column) - else if highlight.column + highlight.word.length >= start.column - # insertion was inside this highlight - @removeHighlight highlight - - else if change.action == "remove" - if start.row == end.row - oldHighlights = (@highlights.rows[start.row] || []).slice(0) - else - rowsRemoved = end.row - start.row - oldHighlights = - (@highlights.rows[start.row] || []).concat( - (@highlights.rows[end.row] || []) - ) - @removeRows start.row + 1, rowsRemoved - - for highlight in oldHighlights - if @_doesHighlightOverlapRange highlight, start, end - @removeHighlight highlight - else if @_isHighlightAfterRange highlight, start, end - @moveHighlight highlight, - row: start.row - column: highlight.column - (end.column - start.column) - - _doesHighlightOverlapRange: (highlight, start, end) -> - highlightIsAllBeforeRange = - highlight.row < start.row or - (highlight.row == start.row and highlight.column + highlight.word.length <= start.column) - highlightIsAllAfterRange = - highlight.row > end.row or - (highlight.row == end.row and highlight.column >= end.column) - !(highlightIsAllBeforeRange or highlightIsAllAfterRange) - - _isHighlightAfterRange: (highlight, start, end) -> - return true if highlight.row > end.row - return false if highlight.row < end.row - highlight.column >= end.column - - - - - - - From 8de226782495666fd2d7f779e2d1f552349a9084 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 30 Apr 2018 16:03:35 +0100 Subject: [PATCH 03/17] Adapt aceEditor to use new spell check manager with adapter --- .../ide/editor/directives/aceEditor.coffee | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index ef3c5b20f8..77a0b94b46 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -7,6 +7,7 @@ define [ "ide/editor/directives/aceEditor/undo/UndoManager" "ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager" "ide/editor/directives/aceEditor/spell-check/SpellCheckManager" + "ide/editor/directives/aceEditor/spell-check/HighlightedWordManager" "ide/editor/directives/aceEditor/highlights/HighlightsManager" "ide/editor/directives/aceEditor/cursor-position/CursorPositionManager" "ide/editor/directives/aceEditor/track-changes/TrackChangesManager" @@ -15,7 +16,7 @@ define [ "ide/graphics/services/graphics" "ide/preamble/services/preamble" "ide/files/services/files" -], (App, Ace, SearchBox, Vim, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) -> +], (App, Ace, SearchBox, Vim, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightedWordManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) -> EditSession = ace.require('ace/edit_session').EditSession ModeList = ace.require('ace/ext/modelist') Vim = ace.require('ace/keyboard/vim').Vim @@ -103,7 +104,8 @@ define [ if scope.spellCheck # only enable spellcheck when explicitly required spellCheckCache = $cacheFactory.get("spellCheck-#{scope.name}") || $cacheFactory("spellCheck-#{scope.name}", {capacity: 1000}) - spellCheckManager = new SpellCheckManager(scope, editor, element, spellCheckCache, $http, $q) + spellCheckManager = new SpellCheckManager(scope, spellCheckCache, $http, $q, new SpellCheckAdapter(editor)) + undoManager = new UndoManager(scope, editor, element) highlightsManager = new HighlightsManager(scope, editor, element) cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage) @@ -361,6 +363,19 @@ define [ session.setScrollTop(session.getScrollTop() + 1) session.setScrollTop(session.getScrollTop() - 1) + onSessionChange = (e) -> + spellCheckManager.onSessionChange() + e.oldSession?.getDocument().off "change", spellCheckManager.onChange + e.session.getDocument().on "change", spellCheckManager.onChange + + attachToSpellCheck = () -> + spellCheckManager.init() + editor.on 'changeSession', onSessionChange + onSessionChange({ session: editor.getSession() }) # Force initial setup + + detachFromSpellCheck = () -> + editor.off 'changeSession', onSessionChange + attachToAce = (sharejs_doc) -> lines = sharejs_doc.getSnapshot().split("\n") session = editor.getSession() @@ -406,6 +421,7 @@ define [ editor.initing = false # now ready to edit document editor.setReadOnly(scope.readOnly) # respect the readOnly setting, normally false + attachToSpellCheck() resetScrollMargins() @@ -467,6 +483,7 @@ define [ scope.$on '$destroy', () -> if scope.sharejsDoc? + detachFromSpellCheck() detachFromAce(scope.sharejsDoc) session = editor.getSession() session?.destroy() @@ -585,3 +602,9 @@ define [ SearchBox::$init = () -> @element = $compile(searchHtml)($rootScope.$new())[0]; $init.apply(@) + + class SpellCheckAdapter + constructor: (@editor) -> + @wordManager = new HighlightedWordManager(@editor) + getLines: () -> @editor.getValue().split('\n') + normalizeChangeEvent: (e) -> e From abcc2cc11bd27bd689100740807affdfabef9a0a Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Tue, 24 Apr 2018 10:27:17 +0100 Subject: [PATCH 04/17] Style codemirror spelling errors --- services/web/public/stylesheets/app/editor/rich-text.less | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/web/public/stylesheets/app/editor/rich-text.less b/services/web/public/stylesheets/app/editor/rich-text.less index 493540e705..37a896269d 100644 --- a/services/web/public/stylesheets/app/editor/rich-text.less +++ b/services/web/public/stylesheets/app/editor/rich-text.less @@ -219,4 +219,11 @@ font-style: italic; color: #999; } + + .spelling-error { + background-image: url(/img/spellcheck-underline.png); + background-repeat: repeat-x; + background-position: bottom; + } } + From e6ffaaa489a68a902a4c77363f7d16ae7339133b Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Thu, 3 May 2018 17:28:36 +0100 Subject: [PATCH 05/17] Handle contextmenu for spelling --- .../ide/editor/directives/aceEditor.coffee | 42 +++++++++++++++---- .../spell-check/HighlightedWordManager.coffee | 17 ++++++++ .../spell-check/SpellCheckManager.coffee | 40 ++++++++++++++++++ 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 77a0b94b46..ad34d4d493 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -20,6 +20,7 @@ define [ EditSession = ace.require('ace/edit_session').EditSession ModeList = ace.require('ace/ext/modelist') Vim = ace.require('ace/keyboard/vim').Vim + Range = ace.require('ace/range').Range # set the path for ace workers if using a CDN (from editor.pug) if window.aceWorkerPath != "" @@ -363,18 +364,22 @@ define [ session.setScrollTop(session.getScrollTop() + 1) session.setScrollTop(session.getScrollTop() - 1) - onSessionChange = (e) -> + onSessionChangeForSpellCheck = (e) -> spellCheckManager.onSessionChange() e.oldSession?.getDocument().off "change", spellCheckManager.onChange e.session.getDocument().on "change", spellCheckManager.onChange + e.oldSession?.off "changeScrollTop", spellCheckManager.onScroll + e.session.on "changeScrollTop", spellCheckManager.onScroll - attachToSpellCheck = () -> + initSpellCheck = () -> spellCheckManager.init() - editor.on 'changeSession', onSessionChange - onSessionChange({ session: editor.getSession() }) # Force initial setup + editor.on 'changeSession', onSessionChangeForSpellCheck + onSessionChangeForSpellCheck({ session: editor.getSession() }) # Force initial setup + editor.on 'nativecontextmenu', spellCheckManager.onContextMenu - detachFromSpellCheck = () -> - editor.off 'changeSession', onSessionChange + tearDownSpellCheck = () -> + editor.off 'changeSession', onSessionChangeForSpellCheck + editor.off 'nativecontextmenu', spellCheckManager.onContextMenu attachToAce = (sharejs_doc) -> lines = sharejs_doc.getSnapshot().split("\n") @@ -421,7 +426,7 @@ define [ editor.initing = false # now ready to edit document editor.setReadOnly(scope.readOnly) # respect the readOnly setting, normally false - attachToSpellCheck() + initSpellCheck() resetScrollMargins() @@ -483,7 +488,7 @@ define [ scope.$on '$destroy', () -> if scope.sharejsDoc? - detachFromSpellCheck() + tearDownSpellCheck() detachFromAce(scope.sharejsDoc) session = editor.getSession() session?.destroy() @@ -608,3 +613,24 @@ define [ @wordManager = new HighlightedWordManager(@editor) getLines: () -> @editor.getValue().split('\n') normalizeChangeEvent: (e) -> e + getCoordsFromContextMenuEvent: (e) -> + e.domEvent.stopPropagation() + return { + x: e.domEvent.clientX, + y: e.domEvent.clientY + } + preventContextMenuEventDefault: (e) -> + e.domEvent.preventDefault() + getHighlightFromCoords: (coords) -> + position = @editor.renderer.screenToTextCoordinates(coords.x, coords.y) + @wordManager.findHighlightWithinRange({ + start: position + end: position + }) + selectHighlightedWord: (highlight) -> + @editor.getSession().getSelection().setSelectionRange( + new Range( + highlight.row, highlight.column, + highlight.row, highlight.column + highlight.word.length + ) + ) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee index ba7561411d..5477bdcafa 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee @@ -51,3 +51,20 @@ define [ row = @highlights.rows[row] for highlight in (row || []).slice() @removeHighlight highlight + + findHighlightWithinRange: (range) -> + rows = @highlights.rows.slice(range.start.row, range.end.row + 1) + for row in rows + for highlight in (row || []) + if @_doesHighlightOverlapRange(highlight, range.start, range.end) + return highlight + return null + + _doesHighlightOverlapRange: (highlight, start, end) -> + highlightIsAllBeforeRange = + highlight.row < start.row or + (highlight.row == start.row and highlight.column + highlight.word.length <= start.column) + highlightIsAllAfterRange = + highlight.row > end.row or + (highlight.row == end.row and highlight.column >= end.column) + !(highlightIsAllBeforeRange or highlightIsAllAfterRange) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index 4dee1faf52..c6b40c7b77 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -1,6 +1,12 @@ define [], () -> class SpellCheckManager constructor: (@$scope, @cache, @$http, @$q, @adapter) -> + @$scope.spellMenu = { + open: false + top: '0px' + left: '0px' + suggestions: [] + } @inProgressRequest = null @updatedLines = [] @@ -30,6 +36,40 @@ define [], () -> @runSpellCheckSoon(200) if @isSpellCheckEnabled() + onContextMenu: (e) => + @closeContextMenu() + @openContextMenu(e) + + onScroll: () => @closeContextMenu() + + openContextMenu: (e) -> + coords = @adapter.getCoordsFromContextMenuEvent(e) + highlight = @adapter.getHighlightFromCoords(coords) + if highlight + @adapter.preventContextMenuEventDefault(e) + @adapter.selectHighlightedWord(highlight) + @$scope.$apply () => + @$scope.spellMenu = { + open: true + top: coords.y + 'px' + left: coords.x + 'px' + suggestions: highlight.suggestions + } + @setUpClickOffContextMenuListener() + return false + + setUpClickOffContextMenuListener: () -> + $(document).one 'click', (e) => + @closeContextMenu() if e.which != 3 # Ignore if right click + return true + + closeContextMenu: () -> + # This is triggered on scroll, so for performance only apply setting when + # it changes + if @$scope?.spellMenu and @$scope.spellMenu.open != false + @$scope.$apply () => + @$scope.spellMenu.open = false + runFullCheck: () -> @adapter.wordManager.reset() @runSpellCheck() if @isSpellCheckEnabled() From cf123ce85727f057906baa9339ec01e4c9f17050 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Thu, 3 May 2018 17:29:05 +0100 Subject: [PATCH 06/17] Extract spellMenu component and use when showing spell suggestions --- .../coffee/ide/editor/EditorManager.coffee | 1 + .../ide/editor/components/spellMenu.coffee | 29 +++++++++++++++++++ .../ide/editor/directives/aceEditor.coffee | 22 ++++---------- 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 services/web/public/coffee/ide/editor/components/spellMenu.coffee diff --git a/services/web/public/coffee/ide/editor/EditorManager.coffee b/services/web/public/coffee/ide/editor/EditorManager.coffee index 72bbe8509a..e3cabf8e98 100644 --- a/services/web/public/coffee/ide/editor/EditorManager.coffee +++ b/services/web/public/coffee/ide/editor/EditorManager.coffee @@ -1,5 +1,6 @@ define [ "ide/editor/Document" + "ide/editor/components/spellMenu" "ide/editor/directives/aceEditor" "ide/editor/directives/toggleSwitch" "ide/editor/controllers/SavingNotificationController" diff --git a/services/web/public/coffee/ide/editor/components/spellMenu.coffee b/services/web/public/coffee/ide/editor/components/spellMenu.coffee new file mode 100644 index 0000000000..c3fd78efba --- /dev/null +++ b/services/web/public/coffee/ide/editor/components/spellMenu.coffee @@ -0,0 +1,29 @@ +define ["base"], (App) -> + App.component "spellMenu", { + bindings: { + open: "<" + top: "<" + left: "<" + suggestions: "<" + replaceWord: "&" + learnWord: "&" + } + template: """ + + """ + } \ No newline at end of file diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index ad34d4d493..a30b93aaa0 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -510,22 +510,12 @@ define [ >Dismiss
- +
Date: Fri, 4 May 2018 15:30:06 +0100 Subject: [PATCH 07/17] Replace word with suggestion and learn word --- .../coffee/ide/editor/components/spellMenu.coffee | 13 +++++++++---- .../coffee/ide/editor/directives/aceEditor.coffee | 9 ++++++++- .../aceEditor/spell-check/SpellCheckManager.coffee | 11 ++++++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/services/web/public/coffee/ide/editor/components/spellMenu.coffee b/services/web/public/coffee/ide/editor/components/spellMenu.coffee index c3fd78efba..ff3462e03e 100644 --- a/services/web/public/coffee/ide/editor/components/spellMenu.coffee +++ b/services/web/public/coffee/ide/editor/components/spellMenu.coffee @@ -4,7 +4,7 @@ define ["base"], (App) -> open: "<" top: "<" left: "<" - suggestions: "<" + highlight: "<" replaceWord: "&" learnWord: "&" } @@ -16,12 +16,17 @@ define ["base"], (App) -> ng-class="{open: $ctrl.open}" >
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index a30b93aaa0..34ffedcac8 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -514,7 +514,9 @@ define [ open="spellMenu.open" top="spellMenu.top" left="spellMenu.left" - suggestions="spellMenu.suggestions" + highlight="spellMenu.highlight" + replace-word="replaceWord(highlight, suggestion)" + learn-word="learnWord(highlight)" >
+ @editor.getSession().replace(new Range( + highlight.row, highlight.column, + highlight.row, highlight.column + highlight.word.length + ), newWord) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index c6b40c7b77..3d274abea4 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -14,6 +14,9 @@ define [], () -> if language != oldLanguage and oldLanguage? @runFullCheck() + @$scope.replaceWord = @adapter.replaceWord + @$scope.learnWord = @learnWord + init: () -> @updatedLines = Array(@adapter.getLines().length).fill(true) @runSpellCheckSoon(200) if @isSpellCheckEnabled() @@ -53,7 +56,7 @@ define [], () -> open: true top: coords.y + 'px' left: coords.x + 'px' - suggestions: highlight.suggestions + highlight: highlight } @setUpClickOffContextMenuListener() return false @@ -70,6 +73,12 @@ define [], () -> @$scope.$apply () => @$scope.spellMenu.open = false + learnWord: (highlight) => + @apiRequest "/learn", word: highlight.word + @adapter.wordManager.removeHighlight highlight + language = @$scope.spellCheckLanguage + @cache?.put("#{language}:#{highlight.word}", true) + runFullCheck: () -> @adapter.wordManager.reset() @runSpellCheck() if @isSpellCheckEnabled() From 9c56f6c2fccb56c59810e00c548f644185bef01f Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Wed, 9 May 2018 16:35:59 +0100 Subject: [PATCH 08/17] Add init test for SpellCheckManager --- .../spell-check/SpellCheckManagerTests.coffee | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee diff --git a/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee b/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee new file mode 100644 index 0000000000..68e47511d3 --- /dev/null +++ b/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee @@ -0,0 +1,46 @@ +define [ + 'ide/editor/directives/aceEditor/spell-check/SpellCheckManager' +], (SpellCheckManager) -> + describe 'SpellCheckManager', -> + beforeEach (done) -> + @timelord = sinon.useFakeTimers() + + window.user = { id: 1 } + window.csrfToken = 'token' + @scope = { + $watch: sinon.stub() + spellCheck: true + spellCheckLanguage: 'en' + } + @wordManager = { + reset: sinon.stub() + clearRow: sinon.stub() + addHighlight: sinon.stub() + } + @adapter = { + getLines: sinon.stub() + wordManager: @wordManager + } + inject ($q, $http, $httpBackend, $cacheFactory) => + @$http = $http + @$q = $q + @$httpBackend = $httpBackend + cache = $cacheFactory('spellCheckTest', {capacity: 1000}) + @spellCheckManager = new SpellCheckManager(@scope, cache, $http, $q, @adapter) + done() + + afterEach -> + @timelord.restore() + + it 'runs a full check soon after init', () -> + @$httpBackend.when('POST', '/spelling/check').respond({ + misspellings: [{ + index: 0 + suggestions: ['opposition'] + }] + }) + @adapter.getLines.returns(['oppozition']) + @spellCheckManager.init() + @timelord.tick(200) + @$httpBackend.flush() + expect(@wordManager.addHighlight).to.have.been.called From d2bba0eb60c2361e337ebedfcf55492458ed10fd Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Fri, 11 May 2018 17:53:22 +0100 Subject: [PATCH 09/17] Fix firefox not closing contextmenu correctly --- .../aceEditor/spell-check/SpellCheckManager.coffee | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index 3d274abea4..6cc95d58c4 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -17,6 +17,10 @@ define [], () -> @$scope.replaceWord = @adapter.replaceWord @$scope.learnWord = @learnWord + $(document).on 'click', (e) => + @closeContextMenu() if e.which != 3 # Ignore if right click + return true + init: () -> @updatedLines = Array(@adapter.getLines().length).fill(true) @runSpellCheckSoon(200) if @isSpellCheckEnabled() @@ -58,14 +62,8 @@ define [], () -> left: coords.x + 'px' highlight: highlight } - @setUpClickOffContextMenuListener() return false - setUpClickOffContextMenuListener: () -> - $(document).one 'click', (e) => - @closeContextMenu() if e.which != 3 # Ignore if right click - return true - closeContextMenu: () -> # This is triggered on scroll, so for performance only apply setting when # it changes From ebf1b7c84c6b3eb42e92cedf7a148c8c44fdd57c Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Fri, 11 May 2018 12:22:45 +0100 Subject: [PATCH 10/17] Extract SpellCheckAdapter to separate file --- .../ide/editor/directives/aceEditor.coffee | 37 +-------------- .../spell-check/SpellCheckAdapter.coffee | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 35 deletions(-) create mode 100644 services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 34ffedcac8..617b41b845 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -7,7 +7,7 @@ define [ "ide/editor/directives/aceEditor/undo/UndoManager" "ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager" "ide/editor/directives/aceEditor/spell-check/SpellCheckManager" - "ide/editor/directives/aceEditor/spell-check/HighlightedWordManager" + "ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter" "ide/editor/directives/aceEditor/highlights/HighlightsManager" "ide/editor/directives/aceEditor/cursor-position/CursorPositionManager" "ide/editor/directives/aceEditor/track-changes/TrackChangesManager" @@ -16,11 +16,10 @@ define [ "ide/graphics/services/graphics" "ide/preamble/services/preamble" "ide/files/services/files" -], (App, Ace, SearchBox, Vim, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightedWordManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) -> +], (App, Ace, SearchBox, Vim, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, SpellCheckAdapter, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) -> EditSession = ace.require('ace/edit_session').EditSession ModeList = ace.require('ace/ext/modelist') Vim = ace.require('ace/keyboard/vim').Vim - Range = ace.require('ace/range').Range # set the path for ace workers if using a CDN (from editor.pug) if window.aceWorkerPath != "" @@ -599,35 +598,3 @@ define [ SearchBox::$init = () -> @element = $compile(searchHtml)($rootScope.$new())[0]; $init.apply(@) - - class SpellCheckAdapter - constructor: (@editor) -> - @wordManager = new HighlightedWordManager(@editor) - getLines: () -> @editor.getValue().split('\n') - normalizeChangeEvent: (e) -> e - getCoordsFromContextMenuEvent: (e) -> - e.domEvent.stopPropagation() - return { - x: e.domEvent.clientX, - y: e.domEvent.clientY - } - preventContextMenuEventDefault: (e) -> - e.domEvent.preventDefault() - getHighlightFromCoords: (coords) -> - position = @editor.renderer.screenToTextCoordinates(coords.x, coords.y) - @wordManager.findHighlightWithinRange({ - start: position - end: position - }) - selectHighlightedWord: (highlight) -> - @editor.getSession().getSelection().setSelectionRange( - new Range( - highlight.row, highlight.column, - highlight.row, highlight.column + highlight.word.length - ) - ) - replaceWord: (highlight, newWord) => - @editor.getSession().replace(new Range( - highlight.row, highlight.column, - highlight.row, highlight.column + highlight.word.length - ), newWord) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee new file mode 100644 index 0000000000..feed646c95 --- /dev/null +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee @@ -0,0 +1,45 @@ +define [ + "ace/ace" + "ide/editor/directives/aceEditor/spell-check/HighlightedWordManager" +], (Ace, HighlightedWordManager) -> + Range = ace.require('ace/range').Range + + class SpellCheckAdapter + constructor: (@editor) -> + @wordManager = new HighlightedWordManager(@editor) + + getLines: () -> + @editor.getValue().split('\n') + + normalizeChangeEvent: (e) -> e + + getCoordsFromContextMenuEvent: (e) -> + e.domEvent.stopPropagation() + return { + x: e.domEvent.clientX, + y: e.domEvent.clientY + } + + preventContextMenuEventDefault: (e) -> + e.domEvent.preventDefault() + + getHighlightFromCoords: (coords) -> + position = @editor.renderer.screenToTextCoordinates(coords.x, coords.y) + @wordManager.findHighlightWithinRange({ + start: position + end: position + }) + + selectHighlightedWord: (highlight) -> + @editor.getSession().getSelection().setSelectionRange( + new Range( + highlight.row, highlight.column, + highlight.row, highlight.column + highlight.word.length + ) + ) + + replaceWord: (highlight, newWord) => + @editor.getSession().replace(new Range( + highlight.row, highlight.column, + highlight.row, highlight.column + highlight.word.length + ), newWord) From 9fa85400b35b061d2a48373f901d32c26a26b4dd Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 14 May 2018 16:28:16 +0100 Subject: [PATCH 11/17] HighlightedWordManager is more naive about tracking position We are relying entirely on Ace's tracking of markers with the anchor trick. This means that we don't have to apply changes to ensure that the word manager data structure tracks which row the highlights are on. This is traded off against slightly less efficient searching/removing --- .../spell-check/HighlightedWordManager.coffee | 59 +++++++++---------- .../spell-check/SpellCheckAdapter.coffee | 16 +++-- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee index 5477bdcafa..627fbb0f88 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee @@ -4,9 +4,7 @@ define [ Range = ace.require("ace/range").Range class Highlight - constructor: (options) -> - @row = options.row - @column = options.column + constructor: (@markerId, @range, options) -> @word = options.word @suggestions = options.suggestions @@ -15,56 +13,53 @@ define [ @reset() reset: () -> - @highlights?.rows.forEach (highlight) => + @highlights?.forEach (highlight) => @editor.getSession().removeMarker(highlight.markerId) - @highlights = rows: [] - - addHighlight: (highlight) -> - unless highlight instanceof Highlight - highlight = new Highlight(highlight) + @highlights = [] + addHighlight: (options) -> session = @editor.getSession() doc = session.getDocument() # Set up Range that will automatically update it's positions when the # document changes range = new Range() range.start = doc.createAnchor({ - row: highlight.row, - column: highlight.column + row: options.row, + column: options.column }) range.end = doc.createAnchor({ - row: highlight.row, - column: highlight.column + highlight.word.length + row: options.row, + column: options.column + options.word.length }) - highlight.markerId = session.addMarker range, "spelling-highlight", 'text', false - @highlights.rows[highlight.row] ||= [] - @highlights.rows[highlight.row].push highlight + markerId = session.addMarker range, "spelling-highlight", 'text', false + + @highlights.push new Highlight(markerId, range, options) removeHighlight: (highlight) -> @editor.getSession().removeMarker(highlight.markerId) - for h, i in @highlights.rows[highlight.row] - if h == highlight - @highlights.rows[highlight.row].splice(i, 1) + @highlights = @highlights.filter (hl) -> + hl != highlight clearRow: (row) -> - row = @highlights.rows[row] - for highlight in (row || []).slice() - @removeHighlight highlight + @highlights.filter (highlight) -> + highlight.range.start.row == row + .forEach (highlight) => + @removeHighlight(highlight) findHighlightWithinRange: (range) -> - rows = @highlights.rows.slice(range.start.row, range.end.row + 1) - for row in rows - for highlight in (row || []) - if @_doesHighlightOverlapRange(highlight, range.start, range.end) - return highlight - return null + @highlights.find (highlight) => + @_doesHighlightOverlapRange highlight, range.start, range.end _doesHighlightOverlapRange: (highlight, start, end) -> + highlightRow = highlight.range.start.row + highlightStartColumn = highlight.range.start.column + highlightEndColumn = highlight.range.end.column + highlightIsAllBeforeRange = - highlight.row < start.row or - (highlight.row == start.row and highlight.column + highlight.word.length <= start.column) + highlightRow < start.row or + (highlightRow == start.row and highlightEndColumn <= start.column) highlightIsAllAfterRange = - highlight.row > end.row or - (highlight.row == end.row and highlight.column >= end.column) + highlightRow > end.row or + (highlightRow == end.row and highlightStartColumn >= end.column) !(highlightIsAllBeforeRange or highlightIsAllAfterRange) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee index feed646c95..51443d9848 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee @@ -31,15 +31,23 @@ define [ }) selectHighlightedWord: (highlight) -> + row = highlight.range.start.row + startColumn = highlight.range.start.column + endColumn = highlight.range.end.column + @editor.getSession().getSelection().setSelectionRange( new Range( - highlight.row, highlight.column, - highlight.row, highlight.column + highlight.word.length + row, startColumn, + row, endColumn ) ) replaceWord: (highlight, newWord) => + row = highlight.range.start.row + startColumn = highlight.range.start.column + endColumn = highlight.range.end.column + @editor.getSession().replace(new Range( - highlight.row, highlight.column, - highlight.row, highlight.column + highlight.word.length + row, startColumn, + row, endColumn ), newWord) From 2be023c731343f192c58f85c873756dbc7b7fece Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 14 May 2018 16:31:45 +0100 Subject: [PATCH 12/17] Prevent spell error marker adding newly typed characters --- .../aceEditor/spell-check/HighlightedWordManager.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee index 627fbb0f88..46b7a218ab 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee @@ -31,6 +31,10 @@ define [ row: options.row, column: options.column + options.word.length }) + # Prevent range from adding newly typed characters to the end of the word. + # This makes it appear as if the spelling error continues to the next word + # even after a space + range.end.$insertRight = true markerId = session.addMarker range, "spelling-highlight", 'text', false From 846f27f0adfcfa3d05360af3a892646a9b97efb2 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Tue, 15 May 2018 15:08:36 +0100 Subject: [PATCH 13/17] Clear highlights that are "touching" the cursor on change This means that correcting a mistake won't wait until the request has resolved and that only the word at the end of the line will have it's spelling highlight removed instead of the entire row --- .../spell-check/HighlightedWordManager.coffee | 21 +++++++++++++++++++ .../spell-check/SpellCheckManager.coffee | 3 +++ 2 files changed, 24 insertions(+) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee index 46b7a218ab..8bb350914f 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee @@ -67,3 +67,24 @@ define [ highlightRow > end.row or (highlightRow == end.row and highlightStartColumn >= end.column) !(highlightIsAllBeforeRange or highlightIsAllAfterRange) + + clearHighlightTouchingRange: (range) -> + highlight = @highlights.find (hl) => + @_doesHighlightTouchRange hl, range.start, range.end + if highlight + @removeHighlight highlight + + _doesHighlightTouchRange: (highlight, start, end) -> + highlightRow = highlight.range.start.row + highlightStartColumn = highlight.range.start.column + highlightEndColumn = highlight.range.end.column + + rangeStartIsWithinHighlight = + highlightStartColumn <= start.column and + highlightEndColumn >= start.column + rangeEndIsWithinHighlight = + highlightStartColumn <= end.column and + highlightEndColumn >= end.column + + highlightRow == start.row and + (rangeStartIsWithinHighlight or rangeEndIsWithinHighlight) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index 6cc95d58c4..2828f4b426 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -35,6 +35,9 @@ define [], () -> onChange: (e) => if @isSpellCheckEnabled() @markLinesAsUpdated(@adapter.normalizeChangeEvent(e)) + + @adapter.wordManager.clearHighlightTouchingRange(e) + @runSpellCheckSoon() onSessionChange: () => From 681e67ecea10a569946c66c7e8ba8157afcd532d Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Thu, 17 May 2018 15:59:13 +0100 Subject: [PATCH 14/17] Be more consistent with naming --- .../aceEditor/spell-check/SpellCheckAdapter.coffee | 4 ++-- .../aceEditor/spell-check/SpellCheckManager.coffee | 14 +++++++------- .../spell-check/SpellCheckManagerTests.coffee | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee index 51443d9848..9b6d809a37 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee @@ -6,7 +6,7 @@ define [ class SpellCheckAdapter constructor: (@editor) -> - @wordManager = new HighlightedWordManager(@editor) + @highlightedWordManager = new HighlightedWordManager(@editor) getLines: () -> @editor.getValue().split('\n') @@ -25,7 +25,7 @@ define [ getHighlightFromCoords: (coords) -> position = @editor.renderer.screenToTextCoordinates(coords.x, coords.y) - @wordManager.findHighlightWithinRange({ + @highlightedWordManager.findHighlightWithinRange({ start: position end: position }) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index 2828f4b426..89e3d1509d 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -36,12 +36,12 @@ define [], () -> if @isSpellCheckEnabled() @markLinesAsUpdated(@adapter.normalizeChangeEvent(e)) - @adapter.wordManager.clearHighlightTouchingRange(e) + @adapter.highlightedWordManager.clearHighlightTouchingRange(e) @runSpellCheckSoon() onSessionChange: () => - @adapter.wordManager.reset() + @adapter.highlightedWordManager.reset() @inProgressRequest.abort() if @inProgressRequest? @runSpellCheckSoon(200) if @isSpellCheckEnabled() @@ -76,12 +76,12 @@ define [], () -> learnWord: (highlight) => @apiRequest "/learn", word: highlight.word - @adapter.wordManager.removeHighlight highlight + @adapter.highlightedWordManager.removeHighlight highlight language = @$scope.spellCheckLanguage @cache?.put("#{language}:#{highlight.word}", true) runFullCheck: () -> - @adapter.wordManager.reset() + @adapter.highlightedWordManager.reset() @runSpellCheck() if @isSpellCheckEnabled() markLinesAsUpdated: (change) -> @@ -146,11 +146,11 @@ define [], () -> displayResult = (highlights) => if linesToProcess? for shouldProcess, row in linesToProcess - @adapter.wordManager.clearRow(row) if shouldProcess + @adapter.highlightedWordManager.clearRow(row) if shouldProcess else - @adapter.wordManager.reset() + @adapter.highlightedWordManager.reset() for highlight in highlights - @adapter.wordManager.addHighlight highlight + @adapter.highlightedWordManager.addHighlight highlight if not words.length displayResult highlights diff --git a/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee b/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee index 68e47511d3..1b0dddced6 100644 --- a/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee +++ b/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee @@ -12,14 +12,14 @@ define [ spellCheck: true spellCheckLanguage: 'en' } - @wordManager = { + @highlightedWordManager = { reset: sinon.stub() clearRow: sinon.stub() addHighlight: sinon.stub() } @adapter = { getLines: sinon.stub() - wordManager: @wordManager + highlightedWordManager: @highlightedWordManager } inject ($q, $http, $httpBackend, $cacheFactory) => @$http = $http @@ -43,4 +43,4 @@ define [ @spellCheckManager.init() @timelord.tick(200) @$httpBackend.flush() - expect(@wordManager.addHighlight).to.have.been.called + expect(@highlightedWordManager.addHighlight).to.have.been.called From a719ac6e6e61e114cd2a1197aa3a2afd8d4f0135 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Fri, 18 May 2018 13:33:06 +0100 Subject: [PATCH 15/17] IE11 doesn't support Array.find so use underscore instead --- .../aceEditor/spell-check/HighlightedWordManager.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee index 8bb350914f..c36edb309c 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee @@ -52,7 +52,7 @@ define [ @removeHighlight(highlight) findHighlightWithinRange: (range) -> - @highlights.find (highlight) => + _.find @highlights, (highlight) => @_doesHighlightOverlapRange highlight, range.start, range.end _doesHighlightOverlapRange: (highlight, start, end) -> @@ -69,7 +69,7 @@ define [ !(highlightIsAllBeforeRange or highlightIsAllAfterRange) clearHighlightTouchingRange: (range) -> - highlight = @highlights.find (hl) => + highlight = _.find @highlights, (hl) => @_doesHighlightTouchRange hl, range.start, range.end if highlight @removeHighlight highlight From c2d7809e055053b583ee958c9b570705dd7688d0 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Fri, 18 May 2018 14:23:36 +0100 Subject: [PATCH 16/17] Add removeWord so that learning word removes all highlights for given word --- .../aceEditor/spell-check/HighlightedWordManager.coffee | 6 ++++++ .../aceEditor/spell-check/SpellCheckManager.coffee | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee index c36edb309c..daeb4cc034 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.coffee @@ -45,6 +45,12 @@ define [ @highlights = @highlights.filter (hl) -> hl != highlight + removeWord: (word) -> + @highlights.filter (highlight) -> + highlight.word == word + .forEach (highlight) => + @removeHighlight(highlight) + clearRow: (row) -> @highlights.filter (highlight) -> highlight.range.start.row == row diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index 89e3d1509d..cbe4fdbd64 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -76,7 +76,7 @@ define [], () -> learnWord: (highlight) => @apiRequest "/learn", word: highlight.word - @adapter.highlightedWordManager.removeHighlight highlight + @adapter.highlightedWordManager.removeWord highlight.word language = @$scope.spellCheckLanguage @cache?.put("#{language}:#{highlight.word}", true) From 309792401f8c6c1239fcb56c1981624367134b86 Mon Sep 17 00:00:00 2001 From: Alasdair Smith Date: Mon, 21 May 2018 10:35:43 +0100 Subject: [PATCH 17/17] Re-focus editor after clicking suggestion --- .../directives/aceEditor/spell-check/SpellCheckAdapter.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee index 9b6d809a37..2afc2cf0ff 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee @@ -51,3 +51,6 @@ define [ row, startColumn, row, endColumn ), newWord) + + # Bring editor back into focus after clicking on suggestion + @editor.focus()