diff --git a/app/main-process/appmenus.js b/app/main-process/appmenus.js index 55afc9cc..eea71a94 100644 --- a/app/main-process/appmenus.js +++ b/app/main-process/appmenus.js @@ -4,6 +4,7 @@ const ipc = electron.ipcMain; const dialog = electron.dialog; const _ = require("lodash"); const Menu = electron.Menu; +const preferences = require("../renderer/preferences.js"); function setupMenus(callbacks) { const template = [ @@ -102,6 +103,23 @@ function setupMenus(callbacks) { accelerator: 'CmdOrCtrl+A', role: 'selectall' }, + { + type: 'separator' + }, + { + label: 'Spell Checking', + type: "checkbox", + checked: preferences.checkSpelling, + click: callbacks.toggleSpelling + }, + { + label: 'Smart Quotes && Dashes', + type: "checkbox", + checked: preferences.smartQuotesAndDashes, + click(item) { + preferences.smartQuotesAndDashes = item.checked; + } + }, ] }, { @@ -140,7 +158,7 @@ function setupMenus(callbacks) { { label: 'Tags visible', type: "checkbox", - checked: true, + checked: preferences.tagsVisible, click: callbacks.toggleTags }, { @@ -260,7 +278,7 @@ function setupMenus(callbacks) { }, ] }); - + var windowMenu = _.find(template, menu => menu.role == "window"); windowMenu.submenu.push( { diff --git a/app/main-process/main.js b/app/main-process/main.js index 00888a2d..43542eff 100644 --- a/app/main-process/main.js +++ b/app/main-process/main.js @@ -2,6 +2,7 @@ const electron = require('electron') const app = electron.app const ipc = electron.ipcMain; const dialog = electron.dialog; +const preferences = require("../renderer/preferences.js"); const ProjectWindow = require("./projectWindow.js").ProjectWindow; const DocumentationWindow = require("./documentationWindow.js").DocumentationWindow; const AboutWindow = require("./aboutWindow.js").AboutWindow; @@ -81,8 +82,13 @@ app.on('ready', function () { if (win) win.exportJSOnly(); }, toggleTags: (item, focusedWindow, event) => { + preferences.tagsVisible = item.checked; focusedWindow.webContents.send("set-tags-visible", item.checked); }, + toggleSpelling: (item, focusedWindow) => { + preferences.checkSpelling = item.checked; + focusedWindow.webContents.send("toggle-spell-check"); + }, nextIssue: (item, focusedWindow) => { focusedWindow.webContents.send("next-issue"); }, diff --git a/app/package-lock.json b/app/package-lock.json index a0fa0954..46d2be8c 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1307,6 +1307,11 @@ } } }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -2610,6 +2615,15 @@ } } }, + "electron-settings": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/electron-settings/-/electron-settings-3.1.4.tgz", + "integrity": "sha512-5AVJGE7+5Fwbui3PKs365vp/vGVpGGXlN1elBx/hAXVazmAtbY2jS0DOD7fBsVxzgCUiRCtR3tQvTF4cYLq/Sw==", + "requires": { + "clone": "2.1.2", + "jsonfile": "4.0.0" + } + }, "env-paths": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", @@ -2763,6 +2777,12 @@ } } }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "optional": true + }, "growl": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", @@ -2820,6 +2840,14 @@ "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", "dev": true }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "4.1.11" + } + }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", diff --git a/app/package.json b/app/package.json index 04c0d0a3..58b0b3e1 100644 --- a/app/package.json +++ b/app/package.json @@ -27,7 +27,7 @@ "devDependencies": { "chai": "^4.1.2", "chai-as-promised": "^7.1.1", - "electron": "^1.4.3", + "electron": "^1.7.6", "markdown-html": "^0.0.8", "mocha": "^4.0.1", "spectron": "^3.2.6" @@ -35,6 +35,8 @@ "dependencies": { "chokidar": "^1.6.0", "fuzzaldrin-plus": "^0.5", + "electron-settings": "^3.1.4", + "glob": "^7.1.2", "inkjs": "^1.6.0", "lodash": "^4.13.1", "mkdirp": "^0.5.1", diff --git a/app/renderer/contextmenu.js b/app/renderer/contextmenu.js index 6aa47652..6d254b46 100644 --- a/app/renderer/contextmenu.js +++ b/app/renderer/contextmenu.js @@ -1,13 +1,54 @@ const {remote} = require('electron') const {Menu, MenuItem} = remote +const SpellChecker = require('./spellChecker.js') -const menu = new Menu() -menu.append(new MenuItem({ role: 'cut' })) -menu.append(new MenuItem({ role: 'copy' })) -menu.append(new MenuItem({ role: 'paste' })) -menu.append(new MenuItem({ role: 'selectall' })) +const maxSpellingSuggestions = 4; + +const playerViewMenu = new Menu() +playerViewMenu.append(new MenuItem({ role: 'copy' })) +playerViewMenu.append(new MenuItem({ role: 'selectall' })) window.addEventListener('contextmenu', (e) => { e.preventDefault() - menu.popup(remote.getCurrentWindow()) -}, false); \ No newline at end of file + + const editor = ace.edit("editor") + if (e.target == editor.textInput.getElement()) { + var template = [ + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + { role: 'selectall' } + ]; + + template = withSpellingSuggestions(template, editor) + + const menu = Menu.buildFromTemplate(template) + menu.popup(remote.getCurrentWindow()) + } else { + playerViewMenu.popup(remote.getCurrentWindow()) + } +}, false); + +function withSpellingSuggestions(template, editor) { + const pos = editor.getCursorPosition() + var suggestions = SpellChecker.getSuggestions(pos) + if (suggestions instanceof Array) { + suggestions = suggestions.slice(0, maxSpellingSuggestions).map(suggestion => { + return { + label: suggestion.word, + click() { + editor.getSession().getDocument().replace(suggestion.where, suggestion.word) + } + } + }) + if (suggestions.length) { + suggestions.push({ type: 'separator' }); + suggestions.push({ label: 'Ignore Spelling', click: () => SpellChecker.ignoreWordAt(pos) }); + suggestions.push({ label: 'Learn Spelling', click: () => SpellChecker.learnWordAt(pos) }); + } else { + suggestions = [{ label: 'No Guesses Found' }] + } + template = suggestions.concat({ type: 'separator' }, template) + } + return template +} diff --git a/app/renderer/controller.js b/app/renderer/controller.js index d43fdea8..43305a06 100644 --- a/app/renderer/controller.js +++ b/app/renderer/controller.js @@ -3,6 +3,7 @@ const ipc = electron.ipcRenderer; const remote = electron.remote; const path = require("path"); const $ = window.jQuery = require('./jquery-2.2.3.min.js'); +const preferences = require('./preferences.js') // Debug const loadTestInk = false; @@ -25,6 +26,8 @@ const LiveCompiler = require("./liveCompiler.js").LiveCompiler; const InkProject = require("./inkProject.js").InkProject; const NavHistory = require("./navHistory.js").NavHistory; const GotoAnything = require("./goto.js").GotoAnything; +const SpellChecker = require("./spellChecker.js"); +const QuotesAndDashes = require("./quotesAndDashes.js"); InkProject.setEvents({ "newProject": (project) => { @@ -37,6 +40,7 @@ InkProject.setEvents({ NavView.setMainInkFilename(filename); NavHistory.reset(); NavHistory.addStep(); + SpellChecker.spellCheck(undefined, 0); }, "didSave": () => { var activeInk = InkProject.currentProject.activeInkFile; @@ -66,6 +70,8 @@ $(document).ready(() => { var testInk = require("fs").readFileSync(path.join(__dirname, "test.ink"), "utf8"); InkProject.currentProject.mainInk.setValue(testInk); } + + setTagsVisible(preferences.tagsVisible); } }); @@ -171,8 +177,10 @@ LiveCompiler.setEvents({ }); EditorView.setEvents({ - "change": () => { + "change": (e) => { LiveCompiler.setEdited(); + SpellChecker.spellCheck(e); + QuotesAndDashes.smarten(e); }, "jumpToSymbol": (symbolName, contextPos) => { var foundSymbol = InkProject.currentProject.findSymbol(symbolName, contextPos); @@ -246,8 +254,12 @@ GotoAnything.setEvents({ }); ipc.on("set-tags-visible", (event, visible) => { - if( visible ) + setTagsVisible(visible); +}); + +function setTagsVisible(visible) { + if (visible) $("#main").removeClass("hideTags"); else $("#main").addClass("hideTags"); -}); \ No newline at end of file +} \ No newline at end of file diff --git a/app/renderer/editorView.js b/app/renderer/editorView.js index e08bf090..7adad8aa 100644 --- a/app/renderer/editorView.js +++ b/app/renderer/editorView.js @@ -16,7 +16,8 @@ var savedScrollRow = null; var events = { change: () => {}, jumpToInclude: () => {}, - jumpToSymbol: () => {} + jumpToSymbol: () => {}, + navigate: () => {} }; editor.setShowPrintMargin(false); @@ -24,8 +25,8 @@ editor.setOptions({ enableBasicAutocompletion: true, enableLiveAutocompletion: true, }); -editor.on("change", () => { - events.change(); +editor.on("change", (e) => { + events.change(e); }); // Exclude language_tools.textCompleter but add the Ink completer @@ -33,7 +34,7 @@ editor.completers = editor.completers.filter( (completer) => completer !== language_tools.textCompleter); editor.completers.push(inkCompleter); -// Unfortunately standard jquery events don't work since +// Unfortunately standard jquery events don't work since // Ace turns pointer events off editor.on("click", function(e){ @@ -89,7 +90,7 @@ editor.on("mousemove", function (e) { } } } - + editor.renderer.setCursorStyle("default"); }); @@ -117,7 +118,7 @@ function addError(error) { var aceClass = "ace-error"; var markerId = editor.session.addMarker( new Range(error.lineNumber-1, 0, error.lineNumber, 0), - editorClass, + editorClass, "line", false ); @@ -157,14 +158,14 @@ exports.EditorView = { editor.focus(); }, focus: () => { editor.focus(); }, - saveCursorPos: () => { - savedCursorPos = editor.getCursorPosition(); - savedScrollRow = editor.getFirstVisibleRow(); + saveCursorPos: () => { + savedCursorPos = editor.getCursorPosition(); + savedScrollRow = editor.getFirstVisibleRow(); }, - restoreCursorPos: () => { + restoreCursorPos: () => { if( savedCursorPos ) { - editor.moveCursorToPosition(savedCursorPos); + editor.moveCursorToPosition(savedCursorPos); editor.scrollToRow(savedScrollRow); - } + } } }; \ No newline at end of file diff --git a/app/renderer/main.css b/app/renderer/main.css index d71720c5..cd230f72 100644 --- a/app/renderer/main.css +++ b/app/renderer/main.css @@ -536,6 +536,12 @@ img.issue-icon.warning { background: #bffff1; } +#editor .ace-misspelled { + border-bottom: dotted 2px red; + margin-bottom: -1px; + position:absolute; +} + #editor .ace_gutter-layer { background: white; border-right: none; @@ -731,7 +737,7 @@ img.issue-icon.warning { } #player td.expressionInput:focus { - outline: none; + outline: none; } #player .evaluationResult { diff --git a/app/renderer/preferences.js b/app/renderer/preferences.js new file mode 100644 index 00000000..8b7ebd46 --- /dev/null +++ b/app/renderer/preferences.js @@ -0,0 +1,20 @@ +const settings = require('electron-settings'); + +const tagsVisibleKey = 'story.tagsVisible'; +const smartQuotesAndDashesKey = 'editor.smartQuotesAndDashes'; +const checkSpellingKey = 'editor.spelling.check'; + +Object.defineProperty(exports, 'tagsVisible', { + get: function () { return settings.get(tagsVisibleKey, true); }, + set: function (value) { settings.set(tagsVisibleKey, value); } +}); + +Object.defineProperty(exports, 'smartQuotesAndDashes', { + get: function () { return settings.get(smartQuotesAndDashesKey, true); }, + set: function (value) { settings.set(smartQuotesAndDashesKey, value); } +}); + +Object.defineProperty(exports, 'checkSpelling', { + get: function () { return settings.get(checkSpellingKey, true); }, + set: function (value) { settings.set(checkSpellingKey, value); } +}); diff --git a/app/renderer/quotesAndDashes.js b/app/renderer/quotesAndDashes.js new file mode 100644 index 00000000..ca933813 --- /dev/null +++ b/app/renderer/quotesAndDashes.js @@ -0,0 +1,90 @@ +const Range = ace.require('ace/range').Range; +const preferences = require("./preferences.js"); +const checkableTypes = require("./spellChecker.js").checkableTypes; + +// TODO: support non en-US quotes? + +var smartening = false; + +exports.smarten = function (e) { + // only continue if enabled and exactly one character or newline was added + if (smartening || + !preferences.smartQuotesAndDashes || + e.action !== 'insert' || + e.lines.length > 2 || + (e.lines.length === 2 && e.lines[0].length !== 0 && e.lines[1].length !== 0) || + (e.lines.length === 1 && e.lines[0].length !== 1)) { + return; + } + + // ...and only continue if we're editing text + const session = ace.edit("editor").getSession(); + const cursorToken = session.getTokenAt(e.end.row, e.end.column); + if (cursorToken && checkableTypes.indexOf(cursorToken.type) === -1) { + return; + } + + smartening = true; + + try { + const document = session.getDocument(); + var range = new Range(e.start.row, e.start.column, e.end.row, e.end.column); + const prevRange = new Range(e.start.row, e.start.column - 1, e.start.row, e.start.column); + const char = document.getTextRange(range); + const prevChar = prevRange.start.column < 0 ? '' : document.getTextRange(prevRange); + + if (prevChar === '-' && char !== '-' && char !== '>') { + document.replace(prevRange, '–'); // en dash + } else if (prevChar === '.' && prevRange.start.column > 1) { + const ellipsisRange = range.extend(prevRange.start.row, prevRange.start.column - 2); + if (document.getTextRange(ellipsisRange) === '...' + char) { + document.replace(ellipsisRange, '…' + char); + range = new Range(ellipsisRange.start.row, ellipsisRange.start.column + 1, ellipsisRange.start.row, ellipsisRange.start.column + 2); + } + } + + switch (char) { + case "'": + if (prevChar === ' ' || prevChar === '') { + document.replace(range, '‘'); + } else { + document.replace(range, '’'); + } + avoidSteppingForward(e, range, document); + break; + case '"': + if (document.getLine(e.start.row).trim().charAt(0) === '{') { + break; + } + if (prevChar === ' ' || prevChar === '') { + document.replace(range, '“'); + } else { + document.replace(range, '”'); + } + avoidSteppingForward(e, range, document); + break; + case '-': + if (prevChar === '-') { + range = range.extend(range.start.row, prevRange.start.column); + document.replace(range, '—'); // em dash + avoidSteppingForward(e, range, document); + } + case '>': + if (prevChar === '–' || prevChar === '—') { // en or em dash + range = range.extend(range.start.row, prevRange.start.column); + document.replace(range, '->'); + avoidSteppingForward(e, range, document); + } + break; + } + } finally { + smartening = false; + } +} + +function avoidSteppingForward(e, range, document) { + const line = document.getLine(range.end.row); + if (line.length > range.end.column) { + e.end.column = e.end.column - 1; + } +} \ No newline at end of file diff --git a/app/renderer/spellChecker.js b/app/renderer/spellChecker.js new file mode 100644 index 00000000..2425058f --- /dev/null +++ b/app/renderer/spellChecker.js @@ -0,0 +1,206 @@ +const ipc = require("electron").ipcRenderer; +const SpellChecker = require("./spellchecker/bindings.js"); +const Range = ace.require('ace/range').Range; +const preferences = require('./preferences.js'); + +const defaultDelay = 500; +const scrollCheckDelay = 2000; +const checkableTypes = [ + "text", + "choice.weaveInsideBrackets", + "todo", + "comment.block.json", + "comment.block.documentation.json", + "comment.line.double-slash.js", + "list-decl.item", + "logic.inline.innerContent", + "logic.sequence.innerContent", + "logic.multiline.innerContent" +]; +exports.checkableTypes = checkableTypes; + +var range; +var previousCursor; +var previousVisibleRow; +var spellcheckTimerID; +var markers = {}; +var ignoredWords = []; + +setInterval(() => { + const firstVisibleRow = ace.edit("editor").getFirstVisibleRow(); + if (firstVisibleRow !== previousVisibleRow) { + exports.spellCheck(); + } + previousVisibleRow = firstVisibleRow; +}, scrollCheckDelay); + +ipc.on("toggle-spell-check", () => { + if (preferences.checkSpelling) { + exports.spellCheck(null, 0); + } else { + const session = ace.edit("editor").getSession(); + for (var markerID in markers) { + session.removeMarker(markerID); + } + markers = {}; + } +}); + +exports.spellCheck = function (scope, delay) { + if (!preferences.checkSpelling) { + return; + } + + if (spellcheckTimerID) { + clearTimeout(spellcheckTimerID); + } + + delay = delay || defaultDelay; + if (range) { + delay = Math.max(delay, defaultDelay); + } + setRange(scope); + + spellcheckTimerID = setTimeout(doSpellCheck, delay); +} + +function setRange(scope) { + const editor = ace.edit("editor"); + const document = editor.getSession().getDocument(); + + if (scope && scope.start && scope.end) { + if (range) { + range = range.extend(scope.start.row, scope.start.column); + range = range.extend(scope.end.row, scope.end.column); + } else { + range = new Range(scope.start.row, scope.start.column, scope.end.row, scope.end.column); + } + } else if (!range) { + const lastRow = document.getLength() - 1; + const lastColumn = Math.max(0, document.getLine(lastRow).length - 1); + range = new Range(0, 0, lastRow, lastColumn); + } + + range = range.clipRows(editor.getFirstVisibleRow(), editor.getLastVisibleRow()); + + while (range.end.row > 0 && range.end.column === 0) { + const endRow = range.end.row - 1; + range.setEnd(endRow, Math.max(0, document.getLine(endRow).length - 1)); + } +} + +function doSpellCheck() { + try { + const editor = ace.edit("editor"); + const session = editor.getSession(); + const cursor = editor.getCursorPosition(); + const lines = session.getDocument().getLines(range.start.row, range.end.row); + var promises = []; + for (var i in lines) { + if (isNaN(Number(i))) { + continue; + } + + const row = range.start.row + Number(i); + + // remove any previous markers for this row + for (var markerID in markers) { + if (markers[markerID].where.start.row === row) { + session.removeMarker(markerID); + delete markers[markerID]; + } + } + + const line = lines[i]; + + promises.push(SpellChecker.checkSpellingAsync(line).then(misspellings => { + for (var j = 0; j < misspellings.length; j++) { + var where = new Range(row, misspellings[j].start, row, misspellings[j].end); + + // don't mark in-progress words as misspelled + if (isTypingWord(cursor, where)) { + continue; + } + + // only check text + const cursorToken = session.getTokenAt(where.start.row, where.start.column + 1); + if (!cursorToken || checkableTypes.indexOf(cursorToken.type) === -1) { + continue; + } + + const word = line.substring(misspellings[j].start, misspellings[j].end); + if (ignoredWords.indexOf(word) !== -1) { + continue; + } + + // anchor the marker in the document + where.start = session.doc.createAnchor(where.start); + where.end = session.doc.createAnchor(where.end); + + var markerID = session.addMarker(where, "ace-misspelled", "typo", false); + markers[markerID] = { where: where, word: word }; + } + })); + + Promise.all(promises).then(_ => previousCursor = cursor); + } + } finally { + range = undefined; + spellcheckTimerID = undefined; + } +} + +function isTypingWord(cursor, where) { + return where.isEnd(cursor.row, cursor.column) && + (!previousCursor || + (previousCursor.row === cursor.row && previousCursor.column === cursor.column - 1)); +} + +exports.getSuggestions = function (pos) { + if (!preferences.checkSpelling) { + return; + } + + var suggestions; + for (var markerID in markers) { + if (markers[markerID].where.contains(pos.row, pos.column)) { + suggestions = SpellChecker.getCorrectionsForMisspelling(markers[markerID].word); + suggestions = suggestions.map(word => { + return { + word: word, + where: markers[markerID].where + } + }); + break; + } + } + return suggestions; +} + +exports.ignoreWordAt = function (pos) { + const word = getWordAt(pos); + ignoredWords.push(word); + removeMarkersForWord(word); +} + +exports.learnWordAt = function (pos) { + const word = getWordAt(pos); + SpellChecker.add(word); + removeMarkersForWord(word); +} + +function getWordAt(pos) { + const session = ace.edit("editor").getSession(); + const range = session.getWordRange(pos.row, pos.column); + return session.getDocument().getTextRange(range); +} + +function removeMarkersForWord(word) { + const session = ace.edit("editor").getSession(); + for (var markerID in markers) { + if (markers[markerID].word === word) { + session.removeMarker(markerID); + delete markers[markerID]; + } + } +} \ No newline at end of file diff --git a/app/renderer/spellchecker/bindings.js b/app/renderer/spellchecker/bindings.js new file mode 100755 index 00000000..2aa25295 --- /dev/null +++ b/app/renderer/spellchecker/bindings.js @@ -0,0 +1,146 @@ +// ----------------------------------------------------------------------------- +// Taken from https://github.com/bartosz-antosik/vscode-spellright by +// Bartosz Antosik. +// +// File contains code taken from SpellChecker Node Module of NODE.js. It +// has been modified to work with various binary bindings without the need +// to rebuild them on target machine. +// +// SpellChecker Node Module: https://github.com/atom/node-spellchecker +// ----------------------------------------------------------------------------- + +'use strict'; + +const path = require('path'); +const glob = require('glob'); + +var bindings = null; +var Spellchecker = null; + +const loadBinary = function (baseName) { + const nodeFiles = glob(path.join(__dirname, `${baseName}*${process.arch}*.node`), { sync: true }); + + var binding = null; + + nodeFiles.forEach((file) => { + try { + if (binding == null) { + binding = require(file); + console.log('SpellRight bindings: \"' + path.basename(file) + '\".'); + } + } catch (e) { + } + }); + + if (!binding) { + console.log('SpellRight found no bindings among these files:'); + nodeFiles.forEach((file) => { + console.log(file); + }); + } + + return binding; +}; + +bindings = loadBinary('spellchecker'); +Spellchecker = bindings.Spellchecker; + +var checkSpellingAsyncCb = Spellchecker.prototype.checkSpellingAsync + +Spellchecker.prototype.checkSpellingAsync = function (corpus) { + return new Promise(function (resolve, reject) { + checkSpellingAsyncCb.call(this, corpus, function (err, result) { + if (err) { + reject(err); + } else { + resolve(result); + } + }); + }.bind(this)); +}; + +var defaultSpellcheck = null; + +var ensureDefaultSpellCheck = function() { + if (defaultSpellcheck) { + return; + } + + var lang = process.env.LANG; + lang = lang ? lang.split('.')[0] : 'en_US'; + defaultSpellcheck = new Spellchecker(); + + setDictionary(lang, getDictionaryPath()); +}; + +var setDictionary = function(lang, dictPath) { + ensureDefaultSpellCheck(); + return defaultSpellcheck.setDictionary(lang, dictPath); +}; + +var isMisspelled = function() { + ensureDefaultSpellCheck(); + + return defaultSpellcheck.isMisspelled.apply(defaultSpellcheck, arguments); +}; + +var checkSpelling = function() { + ensureDefaultSpellCheck(); + + return defaultSpellcheck.checkSpelling.apply(defaultSpellcheck, arguments); +}; + +var checkSpellingAsync = function(corpus) { + ensureDefaultSpellCheck(); + + return defaultSpellcheck.checkSpellingAsync.apply(defaultSpellcheck, arguments); +}; + +var add = function() { + ensureDefaultSpellCheck(); + + defaultSpellcheck.add.apply(defaultSpellcheck, arguments); +}; + +var remove = function() { + ensureDefaultSpellCheck(); + + defaultSpellcheck.remove.apply(defaultSpellcheck, arguments); +}; + +var getCorrectionsForMisspelling = function() { + ensureDefaultSpellCheck(); + + return defaultSpellcheck.getCorrectionsForMisspelling.apply(defaultSpellcheck, arguments); +}; + +var getAvailableDictionaries = function() { + ensureDefaultSpellCheck(); + + return defaultSpellcheck.getAvailableDictionaries.apply(defaultSpellcheck, arguments); +}; + +var getDictionaryPath = function() { + var dict = path.join(__dirname, '..', 'vendor', 'hunspell_dictionaries'); + try { + // HACK: Special case being in an asar archive + var unpacked = dict.replace('.asar' + path.sep, '.asar.unpacked' + path.sep); + if (require('fs').statSyncNoException(unpacked)) { + dict = unpacked; + } + } catch (error) { + } + return dict; +} + +module.exports = { + setDictionary: setDictionary, + add: add, + remove: remove, + isMisspelled: isMisspelled, + checkSpelling: checkSpelling, + checkSpellingAsync: checkSpellingAsync, + getAvailableDictionaries: getAvailableDictionaries, + getCorrectionsForMisspelling: getCorrectionsForMisspelling, + Spellchecker: Spellchecker +}; diff --git a/app/renderer/spellchecker/spellchecker-darwin-1.7.4-x64.node b/app/renderer/spellchecker/spellchecker-darwin-1.7.4-x64.node new file mode 100755 index 00000000..ee2de071 Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-darwin-1.7.4-x64.node differ diff --git a/app/renderer/spellchecker/spellchecker-deb-1.7.4-ia32.node b/app/renderer/spellchecker/spellchecker-deb-1.7.4-ia32.node new file mode 100755 index 00000000..ac9e711b Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-deb-1.7.4-ia32.node differ diff --git a/app/renderer/spellchecker/spellchecker-deb-1.7.4-x64.node b/app/renderer/spellchecker/spellchecker-deb-1.7.4-x64.node new file mode 100755 index 00000000..76a67c6f Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-deb-1.7.4-x64.node differ diff --git a/app/renderer/spellchecker/spellchecker-rpm-1.7.3-ia32.node b/app/renderer/spellchecker/spellchecker-rpm-1.7.3-ia32.node new file mode 100755 index 00000000..db26e0b1 Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-rpm-1.7.3-ia32.node differ diff --git a/app/renderer/spellchecker/spellchecker-rpm-1.7.3-x64.node b/app/renderer/spellchecker/spellchecker-rpm-1.7.3-x64.node new file mode 100755 index 00000000..616c3c8b Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-rpm-1.7.3-x64.node differ diff --git a/app/renderer/spellchecker/spellchecker-rpm-1.7.7-ia32.node b/app/renderer/spellchecker/spellchecker-rpm-1.7.7-ia32.node new file mode 100755 index 00000000..db26e0b1 Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-rpm-1.7.7-ia32.node differ diff --git a/app/renderer/spellchecker/spellchecker-rpm-1.7.7-x64.node b/app/renderer/spellchecker/spellchecker-rpm-1.7.7-x64.node new file mode 100755 index 00000000..616c3c8b Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-rpm-1.7.7-x64.node differ diff --git a/app/renderer/spellchecker/spellchecker-win32-1.7.4-ia32.node b/app/renderer/spellchecker/spellchecker-win32-1.7.4-ia32.node new file mode 100755 index 00000000..46ebea8b Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-win32-1.7.4-ia32.node differ diff --git a/app/renderer/spellchecker/spellchecker-win32-1.7.4-x64.node b/app/renderer/spellchecker/spellchecker-win32-1.7.4-x64.node new file mode 100755 index 00000000..4b2ab80c Binary files /dev/null and b/app/renderer/spellchecker/spellchecker-win32-1.7.4-x64.node differ