ace.define(“ace/ext/chromevox”,, function(require, exports, module) { var cvoxAce = {}; cvoxAce.SpeechProperty; cvoxAce.Cursor; cvoxAce.Token; cvoxAce.Annotation; var CONSTANT_PROP = {
'rate': 0.8, 'pitch': 0.4, 'volume': 0.9
}; var DEFAULT_PROP = {
'rate': 1, 'pitch': 0.5, 'volume': 0.9
}; var ENTITY_PROP = {
'rate': 0.8, 'pitch': 0.8, 'volume': 0.9
}; var KEYWORD_PROP = {
'rate': 0.8, 'pitch': 0.3, 'volume': 0.9
}; var STORAGE_PROP = {
'rate': 0.8, 'pitch': 0.7, 'volume': 0.9
}; var VARIABLE_PROP = {
'rate': 0.8, 'pitch': 0.8, 'volume': 0.9
}; var DELETED_PROP = {
'punctuationEcho': 'none', 'relativePitch': -0.6
}; var ERROR_EARCON = 'ALERT_NONMODAL'; var MODE_SWITCH_EARCON = 'ALERT_MODAL'; var NO_MATCH_EARCON = 'INVALID_KEYPRESS'; var INSERT_MODE_STATE = 'insertMode'; var COMMAND_MODE_STATE = 'start';
var REPLACE_LIST = [
{ substr: ';', newSubstr: ' semicolon ' }, { substr: ':', newSubstr: ' colon ' }
]; var Command = {
SPEAK_ANNOT: 'annots', SPEAK_ALL_ANNOTS: 'all_annots', TOGGLE_LOCATION: 'toggle_location', SPEAK_MODE: 'mode', SPEAK_ROW_COL: 'row_col', TOGGLE_DISPLACEMENT: 'toggle_displacement', FOCUS_TEXT: 'focus_text'
}; var KEY_PREFIX = 'CONTROL + SHIFT '; cvoxAce.editor = null; var lastCursor = null; var annotTable = {}; var shouldSpeakRowLocation = false; var shouldSpeakDisplacement = false; var changed = false; var vimState = null; var keyCodeToShortcutMap = {}; var cmdToShortcutMap = {}; var getKeyShortcutString = function(keyCode) {
return KEY_PREFIX + String.fromCharCode(keyCode);
}; var isVimMode = function() {
var keyboardHandler = cvoxAce.editor.keyBinding.getKeyboardHandler(); return keyboardHandler.$id === 'ace/keyboard/vim';
}; var getCurrentToken = function(cursor) {
return cvoxAce.editor.getSession().getTokenAt(cursor.row, cursor.column + 1);
}; var getCurrentLine = function(cursor) {
return cvoxAce.editor.getSession().getLine(cursor.row);
}; var onRowChange = function(currCursor) {
if (annotTable[currCursor.row]) { cvox.Api.playEarcon(ERROR_EARCON); } if (shouldSpeakRowLocation) { cvox.Api.stop(); speakChar(currCursor); speakTokenQueue(getCurrentToken(currCursor)); speakLine(currCursor.row, 1); } else { speakLine(currCursor.row, 0); }
}; var isWord = function(cursor) {
var line = getCurrentLine(cursor); var lineSuffix = line.substr(cursor.column - 1); if (cursor.column === 0) { lineSuffix = ' ' + line; } var firstWordRegExp = /^\W(\w+)/; var words = firstWordRegExp.exec(lineSuffix); return words !== null;
}; var rules = {
'constant': { prop: CONSTANT_PROP }, 'entity': { prop: ENTITY_PROP }, 'keyword': { prop: KEYWORD_PROP }, 'storage': { prop: STORAGE_PROP }, 'variable': { prop: VARIABLE_PROP }, 'meta': { prop: DEFAULT_PROP, replace: [ { substr: '</', newSubstr: ' closing tag ' }, { substr: '/>', newSubstr: ' close tag ' }, { substr: '<', newSubstr: ' tag start ' }, { substr: '>', newSubstr: ' tag end ' } ] }
}; var DEFAULT_RULE = {
prop: DEFAULT_RULE
}; var expand = function(value, replaceRules) {
var newValue = value; for (var i = 0; i < replaceRules.length; i++) { var replaceRule = replaceRules[i]; var regexp = new RegExp(replaceRule.substr, 'g'); newValue = newValue.replace(regexp, replaceRule.newSubstr); } return newValue;
}; var mergeTokens = function(tokens, start, end) {
var newToken = {}; newToken.value = ''; newToken.type = tokens[start].type; for (var j = start; j < end; j++) { newToken.value += tokens[j].value; } return newToken;
}; var mergeLikeTokens = function(tokens) {
if (tokens.length <= 1) { return tokens; } var newTokens = []; var lastLikeIndex = 0; for (var i = 1; i < tokens.length; i++) { var lastLikeToken = tokens[lastLikeIndex]; var currToken = tokens[i]; if (getTokenRule(lastLikeToken) !== getTokenRule(currToken)) { newTokens.push(mergeTokens(tokens, lastLikeIndex, i)); lastLikeIndex = i; } } newTokens.push(mergeTokens(tokens, lastLikeIndex, tokens.length)); return newTokens;
}; var isRowWhiteSpace = function(row) {
var line = cvoxAce.editor.getSession().getLine(row); var whiteSpaceRegexp = /^\s*$/; return whiteSpaceRegexp.exec(line) !== null;
}; var speakLine = function(row, queue) {
var tokens = cvoxAce.editor.getSession().getTokens(row); if (tokens.length === 0 || isRowWhiteSpace(row)) { cvox.Api.playEarcon('EDITABLE_TEXT'); return; } tokens = mergeLikeTokens(tokens); var firstToken = tokens[0]; tokens = tokens.filter(function(token) { return token !== firstToken; }); speakToken_(firstToken, queue); tokens.forEach(speakTokenQueue);
}; var speakTokenFlush = function(token) {
speakToken_(token, 0);
}; var speakTokenQueue = function(token) {
speakToken_(token, 1);
}; var getTokenRule = function(token) {
if (!token || !token.type) { return; } var split = token.type.split('.'); if (split.length === 0) { return; } var type = split[0]; var rule = rules[type]; if (!rule) { return DEFAULT_RULE; } return rule;
}; var speakToken_ = function(token, queue) {
var rule = getTokenRule(token); var value = expand(token.value, REPLACE_LIST); if (rule.replace) { value = expand(value, rule.replace); } cvox.Api.speak(value, queue, rule.prop);
}; var speakChar = function(cursor) {
var line = getCurrentLine(cursor); cvox.Api.speak(line[cursor.column], 1);
}; var speakDisplacement = function(lastCursor, currCursor) {
var line = getCurrentLine(currCursor); var displace = line.substring(lastCursor.column, currCursor.column); displace = displace.replace(/ /g, ' space '); cvox.Api.speak(displace);
}; var speakCharOrWordOrLine = function(lastCursor, currCursor) {
if (Math.abs(lastCursor.column - currCursor.column) !== 1) { var currLineLength = getCurrentLine(currCursor).length; if (currCursor.column === 0 || currCursor.column === currLineLength) { speakLine(currCursor.row, 0); return; } if (isWord(currCursor)) { cvox.Api.stop(); speakTokenQueue(getCurrentToken(currCursor)); return; } } speakChar(currCursor);
}; var onColumnChange = function(lastCursor, currCursor) {
if (!cvoxAce.editor.selection.isEmpty()) { speakDisplacement(lastCursor, currCursor); cvox.Api.speak('selected', 1); } else if (shouldSpeakDisplacement) { speakDisplacement(lastCursor, currCursor); } else { speakCharOrWordOrLine(lastCursor, currCursor); }
}; var onCursorChange = function(evt) {
if (changed) { changed = false; return; } var currCursor = cvoxAce.editor.selection.getCursor(); if (currCursor.row !== lastCursor.row) { onRowChange(currCursor); } else { onColumnChange(lastCursor, currCursor); } lastCursor = currCursor;
}; var onSelectionChange = function(evt) {
if (cvoxAce.editor.selection.isEmpty()) { cvox.Api.speak('unselected'); }
}; var onChange = function(evt) {
var data = evt.data; switch (data.action) { case 'removeText': cvox.Api.speak(data.text, 0, DELETED_PROP); changed = true; break; case 'insertText': cvox.Api.speak(data.text, 0); changed = true; break; }
}; var isNewAnnotation = function(annot) {
var row = annot.row; var col = annot.column; return !annotTable[row] || !annotTable[row][col];
}; var populateAnnotations = function(annotations) {
annotTable = {}; for (var i = 0; i < annotations.length; i++) { var annotation = annotations[i]; var row = annotation.row; var col = annotation.column; if (!annotTable[row]) { annotTable[row] = {}; } annotTable[row][col] = annotation; }
}; var onAnnotationChange = function(evt) {
var annotations = cvoxAce.editor.getSession().getAnnotations(); var newAnnotations = annotations.filter(isNewAnnotation); if (newAnnotations.length > 0) { cvox.Api.playEarcon(ERROR_EARCON); } populateAnnotations(annotations);
}; var speakAnnot = function(annot) {
var annotText = annot.type + ' ' + annot.text + ' on ' + rowColToString(annot.row, annot.column); annotText = annotText.replace(';', 'semicolon'); cvox.Api.speak(annotText, 1);
}; var speakAnnotsByRow = function(row) {
var annots = annotTable[row]; for (var col in annots) { speakAnnot(annots[col]); }
}; var rowColToString = function(row, col) {
return 'row ' + (row + 1) + ' column ' + (col + 1);
}; var speakCurrRowAndCol = function() {
cvox.Api.speak(rowColToString(lastCursor.row, lastCursor.column));
}; var speakAllAnnots = function() {
for (var row in annotTable) { speakAnnotsByRow(row); }
}; var speakMode = function() {
if (!isVimMode()) { return; } switch (cvoxAce.editor.keyBinding.$data.state) { case INSERT_MODE_STATE: cvox.Api.speak('Insert mode'); break; case COMMAND_MODE_STATE: cvox.Api.speak('Command mode'); break; }
}; var toggleSpeakRowLocation = function() {
shouldSpeakRowLocation = !shouldSpeakRowLocation; if (shouldSpeakRowLocation) { cvox.Api.speak('Speak location on row change enabled.'); } else { cvox.Api.speak('Speak location on row change disabled.'); }
}; var toggleSpeakDisplacement = function() {
shouldSpeakDisplacement = !shouldSpeakDisplacement; if (shouldSpeakDisplacement) { cvox.Api.speak('Speak displacement on column changes.'); } else { cvox.Api.speak('Speak current character or word on column changes.'); }
}; var onKeyDown = function(evt) {
if (evt.ctrlKey && evt.shiftKey) { var shortcut = keyCodeToShortcutMap[evt.keyCode]; if (shortcut) { shortcut.func(); } }
}; var onChangeStatus = function(evt, editor) {
if (!isVimMode()) { return; } var state = editor.keyBinding.$data.state; if (state === vimState) { return; } switch (state) { case INSERT_MODE_STATE: cvox.Api.playEarcon(MODE_SWITCH_EARCON); cvox.Api.setKeyEcho(true); break; case COMMAND_MODE_STATE: cvox.Api.playEarcon(MODE_SWITCH_EARCON); cvox.Api.setKeyEcho(false); break; } vimState = state;
}; var contextMenuHandler = function(evt) {
var cmd = evt.detail['customCommand']; var shortcut = cmdToShortcutMap[cmd]; if (shortcut) { shortcut.func(); cvoxAce.editor.focus(); }
}; var initContextMenu = function() {
var ACTIONS = SHORTCUTS.map(function(shortcut) { return { desc: shortcut.desc + getKeyShortcutString(shortcut.keyCode), cmd: shortcut.cmd }; }); var body = document.querySelector('body'); body.setAttribute('contextMenuActions', JSON.stringify(ACTIONS)); body.addEventListener('ATCustomEvent', contextMenuHandler, true);
}; var onFindSearchbox = function(evt) {
if (evt.match) { speakLine(lastCursor.row, 0); } else { cvox.Api.playEarcon(NO_MATCH_EARCON); }
}; var focus = function() {
cvoxAce.editor.focus();
}; var SHORTCUTS = [
{ keyCode: 49, func: function() { speakAnnotsByRow(lastCursor.row); }, cmd: Command.SPEAK_ANNOT, desc: 'Speak annotations on line' }, { keyCode: 50, func: speakAllAnnots, cmd: Command.SPEAK_ALL_ANNOTS, desc: 'Speak all annotations' }, { keyCode: 51, func: speakMode, cmd: Command.SPEAK_MODE, desc: 'Speak Vim mode' }, { keyCode: 52, func: toggleSpeakRowLocation, cmd: Command.TOGGLE_LOCATION, desc: 'Toggle speak row location' }, { keyCode: 53, func: speakCurrRowAndCol, cmd: Command.SPEAK_ROW_COL, desc: 'Speak row and column' }, { keyCode: 54, func: toggleSpeakDisplacement, cmd: Command.TOGGLE_DISPLACEMENT, desc: 'Toggle speak displacement' }, { keyCode: 55, func: focus, cmd: Command.FOCUS_TEXT, desc: 'Focus text' }
]; var onFocus = function() {
cvoxAce.editor = editor; editor.getSession().selection.on('changeCursor', onCursorChange); editor.getSession().selection.on('changeSelection', onSelectionChange); editor.getSession().on('change', onChange); editor.getSession().on('changeAnnotation', onAnnotationChange); editor.on('changeStatus', onChangeStatus); editor.on('findSearchBox', onFindSearchbox); editor.container.addEventListener('keydown', onKeyDown); lastCursor = editor.selection.getCursor();
}; var init = function(editor) {
onFocus(); SHORTCUTS.forEach(function(shortcut) { keyCodeToShortcutMap[shortcut.keyCode] = shortcut; cmdToShortcutMap[shortcut.cmd] = shortcut; }); editor.on('focus', onFocus); if (isVimMode()) { cvox.Api.setKeyEcho(false); } initContextMenu();
}; function cvoxApiExists() {
return (typeof(cvox) !== 'undefined') && cvox && cvox.Api;
} var tries = 0; var MAX_TRIES = 15; function watchForCvoxLoad(editor) {
if (cvoxApiExists()) { init(editor); } else { tries++; if (tries >= MAX_TRIES) { return; } window.setTimeout(watchForCvoxLoad, 500, editor); }
}
var Editor = require('../editor').Editor; require('../config').defineOptions(Editor.prototype, 'editor', {
enableChromevoxEnhancements: { set: function(val) { if (val) { watchForCvoxLoad(this); } }, value: true // turn it on by default or check for window.cvox }
});
});
(function() { ace.require(["ace/ext/chromevox"], function() {}); })();