diff --git a/demo/chromevox.html b/demo/chromevox.html
new file mode 100644
index 00000000..23c9ba68
--- /dev/null
+++ b/demo/chromevox.html
@@ -0,0 +1,44 @@
+
+
+
+
+ ACE Autocompletion demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/ace/ext/chromevox.js b/lib/ace/ext/chromevox.js
index 396b6bc5..1f51530a 100644
--- a/lib/ace/ext/chromevox.js
+++ b/lib/ace/ext/chromevox.js
@@ -1,843 +1,820 @@
define(function(require, exports, module) {
- /* ChromeVox Ace namespace. */
- var cvoxAce = {};
- /* Typedefs for Closure compiler. */
- /**
- * @typedef {{
- rate: number,
- pitch: number,
- volume: number,
- relativePitch: number,
- punctuationEcho: string
- }}
- */
- /* TODO(peterxiao): Export this typedef through cvox.Api. */
- cvoxAce.SpeechProperty;
+/* ChromeVox Ace namespace. */
+var cvoxAce = {};
- /**
- * @typedef {{
- * row: number,
- * column: number
- * }}
- */
- cvoxAce.Cursor;
+/* Typedefs for Closure compiler. */
+/**
+ * @typedef {{
+ rate: number,
+ pitch: number,
+ volume: number,
+ relativePitch: number,
+ punctuationEcho: string
+ }}
+ */
+/* TODO(peterxiao): Export this typedef through cvox.Api. */
+cvoxAce.SpeechProperty;
- /**
- * @typedef {{
- type: string,
- value: string
- }}
- }
- */
- cvoxAce.Token;
+/**
+ * @typedef {{
+ * row: number,
+ * column: number
+ * }}
+ */
+cvoxAce.Cursor;
- /**
- * These are errors and information that Ace will display in the gutter.
- * @typedef {{
- row: number,
- column: number,
- value: string
- }}
- }
- */
- cvoxAce.Annotation;
+/**
+ * @typedef {{
+ type: string,
+ value: string
+ }}
+ }
+ */
+cvoxAce.Token;
- /* Speech Properties. */
- /**
- * Speech property for speaking constant tokens.
- * @type {cvoxAce.SpeechProperty}
- */
- var CONSTANT_PROP = {
- 'rate': 0.8,
- 'pitch': 0.4,
- 'volume': 0.9
- };
+/**
+ * These are errors and information that Ace will display in the gutter.
+ * @typedef {{
+ row: number,
+ column: number,
+ value: string
+ }}
+ }
+ */
+cvoxAce.Annotation;
- /**
- * Default speech property for speaking tokens.
- * @type {cvoxAce.SpeechProperty}
- */
- var DEFAULT_PROP = {
- 'rate': 1,
- 'pitch': 0.5,
- 'volume': 0.9
- };
+/* Speech Properties. */
+/**
+ * Speech property for speaking constant tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var CONSTANT_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.4,
+ 'volume': 0.9
+};
- /**
- * Speech property for speaking entity tokens.
- * @type {cvoxAce.SpeechProperty}
- */
- var ENTITY_PROP = {
- 'rate': 0.8,
- 'pitch': 0.8,
- 'volume': 0.9
- };
+/**
+ * Default speech property for speaking tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var DEFAULT_PROP = {
+ 'rate': 1,
+ 'pitch': 0.5,
+ 'volume': 0.9
+};
- /**
- * Speech property for speaking keywords.
- * @type {cvoxAce.SpeechProperty}
- */
- var KEYWORD_PROP = {
- 'rate': 0.8,
- 'pitch': 0.3,
- 'volume': 0.9
- };
+/**
+ * Speech property for speaking entity tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var ENTITY_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.8,
+ 'volume': 0.9
+};
- /**
- * Speech property for speaking storage tokens.
- * @type {cvoxAce.SpeechProperty}
- */
- var STORAGE_PROP = {
- 'rate': 0.8,
- 'pitch': 0.7,
- 'volume': 0.9
- };
+/**
+ * Speech property for speaking keywords.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var KEYWORD_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.3,
+ 'volume': 0.9
+};
- /**
- * Speech property for speaking variable tokens.
- * @type {cvoxAce.SpeechProperty}
- */
- var VARIABLE_PROP = {
- 'rate': 0.8,
- 'pitch': 0.8,
- 'volume': 0.9
- };
+/**
+ * Speech property for speaking storage tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var STORAGE_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.7,
+ 'volume': 0.9
+};
- /**
- * Speech property for speaking deleted text.
- * @type {cvoxAce.SpeechProperty}
- */
- var DELETED_PROP = {
- 'punctuationEcho': 'none',
- 'relativePitch': -0.6
- };
+/**
+ * Speech property for speaking variable tokens.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var VARIABLE_PROP = {
+ 'rate': 0.8,
+ 'pitch': 0.8,
+ 'volume': 0.9
+};
- /* Constants for Earcons. */
- var ERROR_EARCON = 'ALERT_NONMODAL';
- var MODE_SWITCH_EARCON = 'ALERT_MODAL';
- var NO_MATCH_EARCON = 'INVALID_KEYPRESS';
+/**
+ * Speech property for speaking deleted text.
+ * @type {cvoxAce.SpeechProperty}
+ */
+var DELETED_PROP = {
+ 'punctuationEcho': 'none',
+ 'relativePitch': -0.6
+};
- /* Constants for vim state. */
- var INSERT_MODE_STATE = 'insertMode';
- var COMMAND_MODE_STATE = 'start';
+/* Constants for Earcons. */
+var ERROR_EARCON = 'ALERT_NONMODAL';
+var MODE_SWITCH_EARCON = 'ALERT_MODAL';
+var NO_MATCH_EARCON = 'INVALID_KEYPRESS';
- /**
- * Context menu commands.
- */
- 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'
- };
+/* Constants for vim state. */
+var INSERT_MODE_STATE = 'insertMode';
+var COMMAND_MODE_STATE = 'start';
- /**
- * Key prefix for each shortcut.
- */
- var KEY_PREFIX = 'CONTROL + SHIFT ';
+/**
+ * Context menu commands.
+ */
+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'
+};
- /* Globals. */
- /**
- * Last cursor position.
- * @type {!cvoxAce.Cursor}
- */
- var lastCursor = ace.selection.getCursor();
+/**
+ * Key prefix for each shortcut.
+ */
+var KEY_PREFIX = 'CONTROL + SHIFT ';
- /**
- * Table of annotations.
- * @typedef {!Object.>}
- */
- var annotTable = {};
+/* Globals. */
+cvoxAce.editor = null;
+/**
+ * Last cursor position.
+ * @type {cvoxAce.Cursor}
+ */
+var lastCursor = null;
- /**
- * Whether to speak character, word, and then line. This allows blind users
- * to know the location of the cursor when they change lines.
- * @typedef {boolean}
- */
- var shouldSpeakRowLocation = false;
+/**
+ * Table of annotations.
+ * @typedef {!Object.>}
+ */
+var annotTable = {};
- /**
- * Whether to speak displacement.
- * @typedef {boolean}
- */
- var shouldSpeakDisplacement = false;
+/**
+ * Whether to speak character, word, and then line. This allows blind users
+ * to know the location of the cursor when they change lines.
+ * @typedef {boolean}
+ */
+var shouldSpeakRowLocation = false;
- /**
- * Whether text was changed to cause a cursor change event.
- * @typedef {boolean}
- */
- var changed = false;
+/**
+ * Whether to speak displacement.
+ * @typedef {boolean}
+ */
+var shouldSpeakDisplacement = false;
- /**
- * Current state vim is in.
- */
- var vimState = null;
+/**
+ * Whether text was changed to cause a cursor change event.
+ * @typedef {boolean}
+ */
+var changed = false;
- /**
- * Mapping from key code to shortcut.
- */
- var keyCodeToShortcutMap = {};
+/**
+ * Current state vim is in.
+ */
+var vimState = null;
- /**
- * Mapping from command to shortcut.
- */
- var cmdToShortcutMap = {};
+/**
+ * Mapping from key code to shortcut.
+ */
+var keyCodeToShortcutMap = {};
- /**
- * Get shortcut string from keyCode.
- * @param {number} keyCode Key code of shortcut.
- * @return {string} String representation of shortcut.
- */
- var getKeyShortcutString = function(keyCode) {
- return KEY_PREFIX + String.fromCharCode(keyCode);
- };
+/**
+ * Mapping from command to shortcut.
+ */
+var cmdToShortcutMap = {};
- /**
- * Return if in vim mode.
- * @return {boolean} True if in Vim mode.
- */
- var isVimMode = function() {
- return ace.keyBinding.getKeyboardHandler().$id === 'ace/keyboard/vim';
- };
+/**
+ * Get shortcut string from keyCode.
+ * @param {number} keyCode Key code of shortcut.
+ * @return {string} String representation of shortcut.
+ */
+var getKeyShortcutString = function(keyCode) {
+ return KEY_PREFIX + String.fromCharCode(keyCode);
+};
- /**
- * Gets the current token.
- * @param {!cvoxAce.Cursor} cursor Current position of the cursor.
- * @return {!cvoxAce.Token} Token at the current position.
- */
- var getCurrentToken = function(cursor) {
- return ace.getSession().getTokenAt(cursor.row, cursor.column + 1);
- };
+/**
+ * Return if in vim mode.
+ * @return {boolean} True if in Vim mode.
+ */
+var isVimMode = function() {
+ var keyboardHandler = cvoxAce.editor.keyBinding.getKeyboardHandler();
+ return keyboardHandler.$id === 'ace/keyboard/vim';
+};
- /**
- * Gets the current line the cursor is under.
- * @param {!cvoxAce.Cursor} cursor Current cursor position.
- */
- var getCurrentLine = function(cursor) {
- return ace.getSession().getLine(cursor.row);
- };
+/**
+ * Gets the current token.
+ * @param {!cvoxAce.Cursor} cursor Current position of the cursor.
+ * @return {!cvoxAce.Token} Token at the current position.
+ */
+var getCurrentToken = function(cursor) {
+ return cvoxAce.editor.getSession().getTokenAt(cursor.row, cursor.column + 1);
+};
- /**
- * Event handler for row changes. When the user changes rows we want to speak
- * the line so the user can work on this line. If shouldSpeakRowLocation is on
- * then we speak the character, then the row, then the line so the user knows
- * where the cursor is.
- * @param {!cvoxAce.Cursor} currCursor Current cursor position.
- */
- var onRowChange = function(currCursor) {
- /* Notify that this line has an annotation. */
- 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);
- }
- };
+/**
+ * Gets the current line the cursor is under.
+ * @param {!cvoxAce.Cursor} cursor Current cursor position.
+ */
+var getCurrentLine = function(cursor) {
+ return cvoxAce.editor.getSession().getLine(cursor.row);
+};
- /**
- * Returns whether the cursor is at the beginning of a word. A word is
- * a grouping of alphanumeric characters including underscores.
- * @param {!cvoxAce.Cursor} cursor Current cursor position.
- * @return {boolean} Whether there is word.
- */
- var isWord = function(cursor) {
- var line = getCurrentLine(cursor);
- var lineSuffix = line.substr(cursor.column - 1);
- if (cursor.column === 0) {
- lineSuffix = ' ' + line;
- }
- /* Use regex to tell if the suffix is at the start of a new word. */
- var firstWordRegExp = /^\W(\w+)/;
- var words = firstWordRegExp.exec(lineSuffix);
- return words !== null;
- };
-
- /**
- * A mapping of syntax type to speech properties.
- */
- var rules = {
- 'constant': CONSTANT_PROP,
- 'entity': ENTITY_PROP,
- 'keyword': KEYWORD_PROP,
- 'storage': STORAGE_PROP,
- 'variable': VARIABLE_PROP
- };
-
- /**
- * Speak the line with syntax properties.
- * @param {number} row Row to speak.
- * @param {number} queue Queue mode to speak.
- */
- var speakLine = function(row, queue) {
- var tokens = ace.getSession().getTokens(row);
- if (tokens.length === 0) {
- return;
- }
- var firstToken = tokens[0];
- /* Filter out first token and spaces. */
- tokens = tokens.filter(function(token) {
- return token !== firstToken && token.type !== 'text';
- });
- /* Speak first token separately to flush if queue. */
- speakToken_(firstToken, queue);
- /* Speak rest of tokens. */
- tokens.forEach(speakTokenQueue);
- };
-
- /**
- * Speak the token based on the syntax of the token, flushing.
- * @param {!cvoxAce.Token} token Token to speak.
- * @param {number} queue Queue mode.
- */
- var speakTokenFlush = function(token) {
- speakToken_(token, 0);
- };
-
- /**
- * Speak the token based on the syntax of the token, queueing.
- * @param {!cvoxAce.Token} token Token to speak.
- * @param {number} queue Queue mode.
- */
- var speakTokenQueue = function(token) {
- speakToken_(token, 1);
- };
-
- /**
- * Speak the token based on the syntax of the token.
- * @private
- * @param {!cvoxAce.Token} token Token to speak.
- * @param {number} queue Queue mode.
- */
- var speakToken_ = function(token, queue) {
- /* Types are period delimited. In this case, we only syntax speak the outer
- * most type of token. */
- if (!token || !token.type) {
- return;
- }
- var split = token.type.split('.');
- if (split.length === 0) {
- return;
- }
- var type = split[0];
- var prop = rules[type];
- if (!prop) {
- prop = DEFAULT_PROP;
- }
- cvox.Api.speak(token.value, queue, prop);
- };
-
- /**
- * Speaks the character under the cursor. This is queued.
- * @param {!cvoxAce.Cursor} cursor Current cursor position.
- * @return {string} Character.
- */
- var speakChar = function(cursor) {
- var line = getCurrentLine(cursor);
- cvox.Api.speak(line[cursor.column], 1);
- };
-
- /**
- * Speaks the jump from lastCursor to currCursor. This function assumes the
- * jump takes place on the current line.
- * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
- * @param {!cvoxAce.Cursor} currCursor Current cursor position.
- */
- var speakDisplacement = function(lastCursor, currCursor) {
+/**
+ * Event handler for row changes. When the user changes rows we want to speak
+ * the line so the user can work on this line. If shouldSpeakRowLocation is on
+ * then we speak the character, then the row, then the line so the user knows
+ * where the cursor is.
+ * @param {!cvoxAce.Cursor} currCursor Current cursor position.
+ */
+var onRowChange = function(currCursor) {
+ /* Notify that this line has an annotation. */
+ if (annotTable[currCursor.row]) {
+ cvox.Api.playEarcon(ERROR_EARCON);
+ }
+ if (shouldSpeakRowLocation) {
cvox.Api.stop();
- var line = getCurrentLine(currCursor);
-
- /* Get the text that we jumped past. */
- var displace = line.substring(lastCursor.column, currCursor.column);
- /* When going forward one space, we speak where we land instead. */
- if (currCursor.column - lastCursor.column === 1) {
- displace = line.substring(lastCursor.column + 1, currCursor.column + 1);
- }
- /* Speak out loud spaces. */
- displace = displace.replace(/ /g, ' space ');
- cvox.Api.speak(displace, 1);
- };
-
- /**
- * Speaks the word if the cursor jumped to a new word or to the beginning
- * of the line. Otherwise speak the charactor.
- * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
- * @param {!cvoxAce.Cursor} currCursor Current cursor position.
- */
- var speakCharOrWordOrLine = function(lastCursor, currCursor) {
- /* Say word only if jump. */
- if (Math.abs(lastCursor.column - currCursor.column) !== 1) {
- var currLineLength = getCurrentLine(currCursor).length;
- /* Speak line if jumping to beginning or end of line. */
- if (currCursor.column === 0 || currCursor.column === currLineLength) {
- speakLine(currCursor.row, 0);
- return;
- }
- if (isWord(currCursor)) {
- cvox.Api.stop();
- speakTokenQueue(getCurrentToken(currCursor));
- return;
- }
- }
speakChar(currCursor);
- };
+ speakTokenQueue(getCurrentToken(currCursor));
+ speakLine(currCursor.row, 1);
+ } else {
+ speakLine(currCursor.row, 0);
+ }
+};
- /**
- * Event handler for column changes. If shouldSpeakDisplacement is on, then
- * we just speak displacements in row changes. Otherwise, we either speak
- * the character for single character movements, the word when jumping to the
- * next word, or the entire line if jumping to beginning or end of the line.
- * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
- * @param {!cvoxAce.Cursor} currCursor Current cursor position.
- */
- var onColumnChange = function(lastCursor, currCursor) {
- if (shouldSpeakDisplacement) {
- speakDisplacement(lastCursor, currCursor);
- } else {
- speakCharOrWordOrLine(lastCursor, currCursor);
- }
- };
+/**
+ * Returns whether the cursor is at the beginning of a word. A word is
+ * a grouping of alphanumeric characters including underscores.
+ * @param {!cvoxAce.Cursor} cursor Current cursor position.
+ * @return {boolean} Whether there is word.
+ */
+var isWord = function(cursor) {
+ var line = getCurrentLine(cursor);
+ var lineSuffix = line.substr(cursor.column - 1);
+ if (cursor.column === 0) {
+ lineSuffix = ' ' + line;
+ }
+ /* Use regex to tell if the suffix is at the start of a new word. */
+ var firstWordRegExp = /^\W(\w+)/;
+ var words = firstWordRegExp.exec(lineSuffix);
+ return words !== null;
+};
- /**
- * Event handler for cursor changes. Classify cursor changes as either row or
- * column changes, then delegate accordingly.
- * @param {!Event} evt The event.
- */
- var onCursorChange = function(evt) {
- /* Do not speak if cursor change was a result of text insertion. We want to
- * speak the text that was inserted and not where the cursor lands. */
- if (changed) {
- changed = false;
+/**
+ * A mapping of syntax type to speech properties.
+ */
+var rules = {
+ 'constant': CONSTANT_PROP,
+ 'entity': ENTITY_PROP,
+ 'keyword': KEYWORD_PROP,
+ 'storage': STORAGE_PROP,
+ 'variable': VARIABLE_PROP
+};
+
+/**
+ * Speak the line with syntax properties.
+ * @param {number} row Row to speak.
+ * @param {number} queue Queue mode to speak.
+ */
+var speakLine = function(row, queue) {
+ var tokens = cvoxAce.editor.getSession().getTokens(row);
+ if (tokens.length === 0) {
+ return;
+ }
+ var firstToken = tokens[0];
+ /* Filter out first token and spaces. */
+ tokens = tokens.filter(function(token) {
+ return token !== firstToken && token.type !== 'text';
+ });
+ /* Speak first token separately to flush if queue. */
+ speakToken_(firstToken, queue);
+ /* Speak rest of tokens. */
+ tokens.forEach(speakTokenQueue);
+};
+
+/**
+ * Speak the token based on the syntax of the token, flushing.
+ * @param {!cvoxAce.Token} token Token to speak.
+ * @param {number} queue Queue mode.
+ */
+var speakTokenFlush = function(token) {
+ speakToken_(token, 0);
+};
+
+/**
+ * Speak the token based on the syntax of the token, queueing.
+ * @param {!cvoxAce.Token} token Token to speak.
+ * @param {number} queue Queue mode.
+ */
+var speakTokenQueue = function(token) {
+ speakToken_(token, 1);
+};
+
+/**
+ * Speak the token based on the syntax of the token.
+ * @private
+ * @param {!cvoxAce.Token} token Token to speak.
+ * @param {number} queue Queue mode.
+ */
+var speakToken_ = function(token, queue) {
+ /* Types are period delimited. In this case, we only syntax speak the outer
+ * most type of token. */
+ if (!token || !token.type) {
+ return;
+ }
+ var split = token.type.split('.');
+ if (split.length === 0) {
+ return;
+ }
+ var type = split[0];
+ var prop = rules[type];
+ if (!prop) {
+ prop = DEFAULT_PROP;
+ }
+ cvox.Api.speak(token.value, queue, prop);
+};
+
+/**
+ * Speaks the character under the cursor. This is queued.
+ * @param {!cvoxAce.Cursor} cursor Current cursor position.
+ * @return {string} Character.
+ */
+var speakChar = function(cursor) {
+ var line = getCurrentLine(cursor);
+ cvox.Api.speak(line[cursor.column], 1);
+};
+
+/**
+ * Speaks the jump from lastCursor to currCursor. This function assumes the
+ * jump takes place on the current line.
+ * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
+ * @param {!cvoxAce.Cursor} currCursor Current cursor position.
+ */
+var speakDisplacement = function(lastCursor, currCursor) {
+ cvox.Api.stop();
+ var line = getCurrentLine(currCursor);
+
+ /* Get the text that we jumped past. */
+ var displace = line.substring(lastCursor.column, currCursor.column);
+ /* When going forward one space, we speak where we land instead. */
+ if (currCursor.column - lastCursor.column === 1) {
+ displace = line.substring(lastCursor.column + 1, currCursor.column + 1);
+ }
+ /* Speak out loud spaces. */
+ displace = displace.replace(/ /g, ' space ');
+ cvox.Api.speak(displace, 1);
+};
+
+/**
+ * Speaks the word if the cursor jumped to a new word or to the beginning
+ * of the line. Otherwise speak the charactor.
+ * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
+ * @param {!cvoxAce.Cursor} currCursor Current cursor position.
+ */
+var speakCharOrWordOrLine = function(lastCursor, currCursor) {
+ /* Say word only if jump. */
+ if (Math.abs(lastCursor.column - currCursor.column) !== 1) {
+ var currLineLength = getCurrentLine(currCursor).length;
+ /* Speak line if jumping to beginning or end of line. */
+ if (currCursor.column === 0 || currCursor.column === currLineLength) {
+ speakLine(currCursor.row, 0);
return;
}
- var currCursor = ace.selection.getCursor();
- if (currCursor.row !== lastCursor.row) {
- onRowChange(currCursor);
- } else {
- onColumnChange(lastCursor, currCursor);
- }
- lastCursor = currCursor;
- };
-
- /**
- * Event handler for source changes. We want auditory feedback for inserting
- * and deleting text.
- * @param {!Event} evt The event.
- */
- var onChange = function(evt) {
- var data = evt.data;
- switch (data.action) {
- case 'removeText':
- cvox.Api.speak(data.text, 0, DELETED_PROP);
- /* Let the future cursor change event know it's from text change. */
- changed = true;
- break;
- case 'insertText':
- cvox.Api.speak(data.text, 0);
- /* Let the future cursor change event know it's from text change. */
- changed = true;
- break;
- }
- };
-
- /**
- * Returns whether or not the annotation is new.
- * @param {!cvoxAce.Annotation} annot Annotation in question.
- * @return {boolean} Whether annot is new.
- */
- var isNewAnnotation = function(annot) {
- var row = annot.row;
- var col = annot.column;
- return !annotTable[row] || !annotTable[row][col];
- };
-
- /**
- * Populates the annotation table.
- * @param {!Array.} annotations Array of annotations.
- */
- 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;
- }
- };
-
- /**
- * Event handler for annotation changes. We want to notify the user when an
- * a new annotation appears.
- * @param {!Event} evt Event.
- */
- var onAnnotationChange = function(evt) {
- var annotations = ace.getSession().getAnnotations();
- var newAnnotations = annotations.filter(isNewAnnotation);
- if (newAnnotations.length > 0) {
- cvox.Api.playEarcon(ERROR_EARCON);
- }
- populateAnnotations(annotations);
- };
-
- /**
- * Speak annotation.
- * @param {!cvoxAce.Annotation} annot Annotation to speak.
- */
- 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);
- };
-
- /**
- * Speak annotations in a row.
- * @param {number} row Row of annotations to speak.
- */
- var speakAnnotsByRow = function(row) {
- var annots = annotTable[row];
- for (var col in annots) {
- speakAnnot(annots[col]);
- }
- };
-
- /**
- * Get a string representation of a row and column.
- * @param {boolean} row Zero indexed row.
- * @param {boolean} col Zero indexed column.
- * @return {string} Row and column to be spoken.
- */
- var rowColToString = function(row, col) {
- return 'row ' + (row + 1) + ' column ' + (col + 1);
- };
-
- /**
- * Speaks the row and column.
- */
- var speakCurrRowAndCol = function() {
- cvox.Api.speak(rowColToString(lastCursor.row, lastCursor.column));
- };
-
- /**
- * Speaks all annotations.
- */
- var speakAllAnnots = function() {
- for (var row in annotTable) {
- speakAnnotsByRow(row);
- }
- };
-
- /**
- * Speak the vim mode. If no vim mode, this function does nothing.
- */
- var speakMode = function() {
- if (!isVimMode()) {
+ if (isWord(currCursor)) {
+ cvox.Api.stop();
+ speakTokenQueue(getCurrentToken(currCursor));
return;
}
- switch (ace.keyBinding.$data.state) {
- case INSERT_MODE_STATE:
- cvox.Api.speak('Insert mode');
- break;
- case COMMAND_MODE_STATE:
- cvox.Api.speak('Command mode');
- break;
- }
- };
+ }
+ speakChar(currCursor);
+};
- /**
- * Toggle speak location.
- */
- var toggleSpeakRowLocation = function() {
- shouldSpeakRowLocation = !shouldSpeakRowLocation;
- /* Auditory feedback of the change. */
- if (shouldSpeakRowLocation) {
- cvox.Api.speak('Speak location on row change enabled.');
- } else {
- cvox.Api.speak('Speak location on row change disabled.');
- }
- };
+/**
+ * Event handler for column changes. If shouldSpeakDisplacement is on, then
+ * we just speak displacements in row changes. Otherwise, we either speak
+ * the character for single character movements, the word when jumping to the
+ * next word, or the entire line if jumping to beginning or end of the line.
+ * @param {!cvoxAce.Cursor} lastCursor Previous cursor position.
+ * @param {!cvoxAce.Cursor} currCursor Current cursor position.
+ */
+var onColumnChange = function(lastCursor, currCursor) {
+ if (shouldSpeakDisplacement) {
+ speakDisplacement(lastCursor, currCursor);
+ } else {
+ speakCharOrWordOrLine(lastCursor, currCursor);
+ }
+};
- /**
- * Toggle speak displacement.
- */
- var toggleSpeakDisplacement = function() {
- speakDisplacement = !speakDisplacement;
- /* Auditory feedback of the change. */
- if (speakDisplacement) {
- cvox.Api.speak('Speak displacement on column changes.');
- } else {
- cvox.Api.speak('Speak current character or word on column changes.');
- }
- };
+/**
+ * Event handler for cursor changes. Classify cursor changes as either row or
+ * column changes, then delegate accordingly.
+ * @param {!Event} evt The event.
+ */
+var onCursorChange = function(evt) {
+ /* Do not speak if cursor change was a result of text insertion. We want to
+ * speak the text that was inserted and not where the cursor lands. */
+ if (changed) {
+ changed = false;
+ return;
+ }
+ var currCursor = cvoxAce.editor.selection.getCursor();
+ if (currCursor.row !== lastCursor.row) {
+ onRowChange(currCursor);
+ } else {
+ onColumnChange(lastCursor, currCursor);
+ }
+ lastCursor = currCursor;
+};
- /**
- * Event handler for key down events. Gets the right shortcut from the map,
- * and calls the associated function.
- * @param {!Event} evt Keyboard event.
- */
- var onKeyDown = function(evt) {
- if (evt.ctrlKey && evt.shiftKey) {
- var shortcut = keyCodeToShortcutMap[evt.keyCode];
- if (shortcut) {
- shortcut.func();
- }
- }
- };
+/**
+ * Event handler for source changes. We want auditory feedback for inserting
+ * and deleting text.
+ * @param {!Event} evt The event.
+ */
+var onChange = function(evt) {
+ var data = evt.data;
+ switch (data.action) {
+ case 'removeText':
+ cvox.Api.speak(data.text, 0, DELETED_PROP);
+ /* Let the future cursor change event know it's from text change. */
+ changed = true;
+ break;
+ case 'insertText':
+ cvox.Api.speak(data.text, 0);
+ /* Let the future cursor change event know it's from text change. */
+ changed = true;
+ break;
+ }
+};
- /**
- * Event handler for status change events. Auditory feedback of changing
- * between vim states.
- * @param {!Event} evt Change status event.
- * @param {!Object} editor Editor state.
- */
- var onChangeStatus = function(evt, editor) {
- if (!isVimMode()) {
- return;
- }
- var state = editor.keyBinding.$data.state;
- if (state === vimState) {
- /* State hasn't changed, do nothing. */
- return;
- }
- switch (state) {
- case INSERT_MODE_STATE:
- cvox.Api.playEarcon(MODE_SWITCH_EARCON);
- /* When in insert mode, we want to speak out keys as feedback. */
- cvox.Api.setKeyEcho(true);
- break;
- case COMMAND_MODE_STATE:
- cvox.Api.playEarcon(MODE_SWITCH_EARCON);
- /* When in command mode, we want don't speak out keys because those keys
- * are not being inserted in the document. */
- cvox.Api.setKeyEcho(false);
- break;
- }
- vimState = state;
- };
+/**
+ * Returns whether or not the annotation is new.
+ * @param {!cvoxAce.Annotation} annot Annotation in question.
+ * @return {boolean} Whether annot is new.
+ */
+var isNewAnnotation = function(annot) {
+ var row = annot.row;
+ var col = annot.column;
+ return !annotTable[row] || !annotTable[row][col];
+};
- /**
- * Handles context menu events. This is a ChromeVox feature where hitting
- * the shortcut ChromeVox + comma will open up a search bar where you can
- * type in various commands. All keyboard shortcuts are also commands that
- * can be invoked. This handles the event that ChromeVox sends to the page.
- * @param {Event} evt Event received.
- */
- var contextMenuHandler = function(evt) {
- var cmd = evt.detail['customCommand'];
- var shortcut = cmdToShortcutMap[cmd];
+/**
+ * Populates the annotation table.
+ * @param {!Array.} annotations Array of annotations.
+ */
+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;
+ }
+};
+
+/**
+ * Event handler for annotation changes. We want to notify the user when an
+ * a new annotation appears.
+ * @param {!Event} evt Event.
+ */
+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);
+};
+
+/**
+ * Speak annotation.
+ * @param {!cvoxAce.Annotation} annot Annotation to speak.
+ */
+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);
+};
+
+/**
+ * Speak annotations in a row.
+ * @param {number} row Row of annotations to speak.
+ */
+var speakAnnotsByRow = function(row) {
+ var annots = annotTable[row];
+ for (var col in annots) {
+ speakAnnot(annots[col]);
+ }
+};
+
+/**
+ * Get a string representation of a row and column.
+ * @param {boolean} row Zero indexed row.
+ * @param {boolean} col Zero indexed column.
+ * @return {string} Row and column to be spoken.
+ */
+var rowColToString = function(row, col) {
+ return 'row ' + (row + 1) + ' column ' + (col + 1);
+};
+
+/**
+ * Speaks the row and column.
+ */
+var speakCurrRowAndCol = function() {
+ cvox.Api.speak(rowColToString(lastCursor.row, lastCursor.column));
+};
+
+/**
+ * Speaks all annotations.
+ */
+var speakAllAnnots = function() {
+ for (var row in annotTable) {
+ speakAnnotsByRow(row);
+ }
+};
+
+/**
+ * Speak the vim mode. If no vim mode, this function does nothing.
+ */
+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;
+ }
+};
+
+/**
+ * Toggle speak location.
+ */
+var toggleSpeakRowLocation = function() {
+ shouldSpeakRowLocation = !shouldSpeakRowLocation;
+ /* Auditory feedback of the change. */
+ if (shouldSpeakRowLocation) {
+ cvox.Api.speak('Speak location on row change enabled.');
+ } else {
+ cvox.Api.speak('Speak location on row change disabled.');
+ }
+};
+
+/**
+ * Toggle speak displacement.
+ */
+var toggleSpeakDisplacement = function() {
+ speakDisplacement = !speakDisplacement;
+ /* Auditory feedback of the change. */
+ if (speakDisplacement) {
+ cvox.Api.speak('Speak displacement on column changes.');
+ } else {
+ cvox.Api.speak('Speak current character or word on column changes.');
+ }
+};
+
+/**
+ * Event handler for key down events. Gets the right shortcut from the map,
+ * and calls the associated function.
+ * @param {!Event} evt Keyboard event.
+ */
+var onKeyDown = function(evt) {
+ if (evt.ctrlKey && evt.shiftKey) {
+ var shortcut = keyCodeToShortcutMap[evt.keyCode];
if (shortcut) {
shortcut.func();
- /* ChromeVox will bring focus to an element near the cursor instead of the
- * text input. */
- ace.focus();
}
- };
+ }
+};
- /**
- * Initialize the ChromeVox context menu.
- */
- var initContextMenu = function() {
- var ACTIONS = SHORTCUTS.map(function(shortcut) {
- return {
- desc: shortcut.desc + getKeyShortcutString(shortcut.keyCode),
- cmd: shortcut.cmd
- };
- });
+/**
+ * Event handler for status change events. Auditory feedback of changing
+ * between vim states.
+ * @param {!Event} evt Change status event.
+ * @param {!Object} editor Editor state.
+ */
+var onChangeStatus = function(evt, editor) {
+ if (!isVimMode()) {
+ return;
+ }
+ var state = editor.keyBinding.$data.state;
+ if (state === vimState) {
+ /* State hasn't changed, do nothing. */
+ return;
+ }
+ switch (state) {
+ case INSERT_MODE_STATE:
+ cvox.Api.playEarcon(MODE_SWITCH_EARCON);
+ /* When in insert mode, we want to speak out keys as feedback. */
+ cvox.Api.setKeyEcho(true);
+ break;
+ case COMMAND_MODE_STATE:
+ cvox.Api.playEarcon(MODE_SWITCH_EARCON);
+ /* When in command mode, we want don't speak out keys because those keys
+ * are not being inserted in the document. */
+ cvox.Api.setKeyEcho(false);
+ break;
+ }
+ vimState = state;
+};
- /* Attach ContextMenuActions. */
- var body = document.querySelector('body');
- body.setAttribute('contextMenuActions', JSON.stringify(ACTIONS));
+/**
+ * Handles context menu events. This is a ChromeVox feature where hitting
+ * the shortcut ChromeVox + comma will open up a search bar where you can
+ * type in various commands. All keyboard shortcuts are also commands that
+ * can be invoked. This handles the event that ChromeVox sends to the page.
+ * @param {Event} evt Event received.
+ */
+var contextMenuHandler = function(evt) {
+ var cmd = evt.detail['customCommand'];
+ var shortcut = cmdToShortcutMap[cmd];
+ if (shortcut) {
+ shortcut.func();
+ /* ChromeVox will bring focus to an element near the cursor instead of the
+ * text input. */
+ cvoxAce.editor.focus();
+ }
+};
- /* Listen for ContextMenu events. */
- body.addEventListener('ATCustomEvent', contextMenuHandler, true);
- };
-
- /**
- * Returns a mutations handler where f is applied to each mutation.
- * @param {function} f Function to be applied to mutations.
- */
- var getMutationHandler = function(f) {
- return function(mutations) {
- mutations.forEach(f);
+/**
+ * Initialize the ChromeVox context menu.
+ */
+var initContextMenu = function() {
+ var ACTIONS = SHORTCUTS.map(function(shortcut) {
+ return {
+ desc: shortcut.desc + getKeyShortcutString(shortcut.keyCode),
+ cmd: shortcut.cmd
};
- };
+ });
- /**
- * Watches and handles the mutation that is a result of a search.
- * @param {Mutation} m Mutation.
- */
- var watchForSearch = function(m) {
- if (m.attributeName === 'class' &&
- m.target.className === 'ace_search_form ace_nomatch') {
- /* No match, give auditory feedback! */
- cvox.Api.playEarcon(NO_MATCH_EARCON);
- } else {
- /* There is still a match! Speak the line. */
- speakLine(lastCursor.row, 0);
- }
- };
+ /* Attach ContextMenuActions. */
+ var body = document.querySelector('body');
+ body.setAttribute('contextMenuActions', JSON.stringify(ACTIONS));
- /**
- * Configuration for mutation observer.
- */
- var MO_CONFIG = { attributes: true, childList: true, characterData: true};
+ /* Listen for ContextMenu events. */
+ body.addEventListener('ATCustomEvent', contextMenuHandler, true);
+};
- /**
- * Watches and handles the mutation that adds the search bar to the DOM.
- */
- var watchForStartSearch = function(m) {
- for (var i = 0; i < m.addedNodes.length; i++) {
- if (m.addedNodes.item(i).className === 'ace_search right') {
- var mutationHandler = getMutationHandler(watchForSearch);
- var searchObs = new MutationObserver(mutationHandler);
- var target = m.addedNodes.item(i).querySelector('.ace_search_form');
- searchObs.observe(target, MO_CONFIG);
- }
- }
- };
-
- /**
- * Focus to text input.
- */
- var focus = function() {
- ace.focus();
- };
-
- /**
- * Shortcut definitions.
- */
- var SHORTCUTS = [
- {
- /* 1 key. */
- keyCode: 49,
- func: function() {
- speakAnnotsByRow(lastCursor.row);
- },
- cmd: Command.SPEAK_ANNOT,
- desc: 'Speak annotations on line'
- },
- {
- /* 2 key. */
- keyCode: 50,
- func: speakAllAnnots,
- cmd: Command.SPEAK_ALL_ANNOTS,
- desc: 'Speak all annotations'
- },
- {
- /* 3 key. */
- keyCode: 51,
- func: speakMode,
- cmd: Command.SPEAK_MODE,
- desc: 'Speak Vim mode'
- },
- {
- /* 4 key. */
- keyCode: 52,
- func: toggleSpeakRowLocation,
- cmd: Command.TOGGLE_LOCATION,
- desc: 'Toggle speak row location'
- },
- {
- /* 5 key. */
- keyCode: 53,
- func: speakCurrRowAndCol,
- cmd: Command.SPEAK_ROW_COL,
- desc: 'Speak row and column'
- },
- {
- /* 6 key. */
- keyCode: 54,
- func: toggleSpeakDisplacement,
- cmd: Command.TOGGLE_DISPLACEMENT,
- desc: 'Toggle speak displacement'
- },
- {
- /* 7 key. */
- keyCode: 55,
- func: focus,
- cmd: Command.FOCUS_TEXT,
- desc: 'Focus text'
- }
- ];
-
- /**
- * Initialize the theme.
- */
- var init = function() {
- /* Construct maps. */
- SHORTCUTS.forEach(function(shortcut) {
- keyCodeToShortcutMap[shortcut.keyCode] = shortcut;
- cmdToShortcutMap[shortcut.cmd] = shortcut;
- });
-
- /* Set up listeners. */
- ace.getSession().selection.on('changeCursor', onCursorChange);
- ace.getSession().on('change', onChange);
- ace.getSession().on('changeAnnotation', onAnnotationChange);
- ace.on('changeStatus', onChangeStatus);
- window.addEventListener('keydown', onKeyDown);
-
- /* Assume we start in command mode if vim. */
- if (isVimMode()) {
- cvox.Api.setKeyEcho(false);
- }
- initContextMenu();
-
- var target = document.querySelector('.ace_editor');
- var mutationHandler = getMutationHandler(watchForStartSearch);
- var observer = new MutationObserver(mutationHandler);
-
- observer.observe(target, MO_CONFIG);
-
- ace.focus();
- };
-
- /**
- * Returns if cvox exists, and the api exists.
- * @return {boolean} Whether not Cvox Api exists.
- */
- function cvoxApiExists() {
- return (typeof(cvox) !== 'undefined') && cvox && cvox.Api;
+var onFindSearchbox = function(evt) {
+ if (evt.match) {
+ /* There is still a match! Speak the line. */
+ speakLine(lastCursor.row, 0);
+ } else {
+ /* No match, give auditory feedback! */
+ cvox.Api.playEarcon(NO_MATCH_EARCON);
}
+};
- /**
- * Number of tries for Cvox loading.
- * @type {number}
- */
- var tries = 0;
+/**
+ * Focus to text input.
+ */
+var focus = function() {
+ cvoxAce.editor.focus();
+};
- /**
- * Max number of tries to watch for Cvox loading.
- * @type {number}
- */
- var MAX_TRIES = 15;
-
- /**
- * Check for ChromeVox load.
- */
- function watchForCvoxLoad() {
- if (cvoxApiExists()) {
- init();
- } else {
- tries++;
- if (tries >= MAX_TRIES) {
- return;
- }
- window.setTimeout(watchForCvoxLoad, 500);
- }
+/**
+ * Shortcut definitions.
+ */
+var SHORTCUTS = [
+ {
+ /* 1 key. */
+ keyCode: 49,
+ func: function() {
+ speakAnnotsByRow(lastCursor.row);
+ },
+ cmd: Command.SPEAK_ANNOT,
+ desc: 'Speak annotations on line'
+ },
+ {
+ /* 2 key. */
+ keyCode: 50,
+ func: speakAllAnnots,
+ cmd: Command.SPEAK_ALL_ANNOTS,
+ desc: 'Speak all annotations'
+ },
+ {
+ /* 3 key. */
+ keyCode: 51,
+ func: speakMode,
+ cmd: Command.SPEAK_MODE,
+ desc: 'Speak Vim mode'
+ },
+ {
+ /* 4 key. */
+ keyCode: 52,
+ func: toggleSpeakRowLocation,
+ cmd: Command.TOGGLE_LOCATION,
+ desc: 'Toggle speak row location'
+ },
+ {
+ /* 5 key. */
+ keyCode: 53,
+ func: speakCurrRowAndCol,
+ cmd: Command.SPEAK_ROW_COL,
+ desc: 'Speak row and column'
+ },
+ {
+ /* 6 key. */
+ keyCode: 54,
+ func: toggleSpeakDisplacement,
+ cmd: Command.TOGGLE_DISPLACEMENT,
+ desc: 'Toggle speak displacement'
+ },
+ {
+ /* 7 key. */
+ keyCode: 55,
+ func: focus,
+ cmd: Command.FOCUS_TEXT,
+ desc: 'Focus text'
}
+];
+
+/**
+ * Initialize the theme.
+ */
+var init = function(editor) {
+ cvoxAce.editor = editor;
+ lastCursor = editor.selection.getCursor();
+ /* Construct maps. */
+ SHORTCUTS.forEach(function(shortcut) {
+ keyCodeToShortcutMap[shortcut.keyCode] = shortcut;
+ cmdToShortcutMap[shortcut.cmd] = shortcut;
+ });
+
+ /* Set up listeners. */
+ cvoxAce.editor.getSession().selection.on('changeCursor', onCursorChange);
+ cvoxAce.editor.getSession().on('change', onChange);
+ cvoxAce.editor.getSession().on('changeAnnotation', onAnnotationChange);
+ cvoxAce.editor.on('changeStatus', onChangeStatus);
+ cvoxAce.editor.on('findSearchBox', onFindSearchbox);
+ window.addEventListener('keydown', onKeyDown);
+
+ /* Assume we start in command mode if vim. */
+ if (isVimMode()) {
+ cvox.Api.setKeyEcho(false);
+ }
+ initContextMenu();
+
+ cvoxAce.editor.focus();
+};
+
+/**
+ * Returns if cvox exists, and the api exists.
+ * @return {boolean} Whether not Cvox Api exists.
+ */
+function cvoxApiExists() {
+ return (typeof(cvox) !== 'undefined') && cvox && cvox.Api;
+}
+
+/**
+ * Number of tries for Cvox loading.
+ * @type {number}
+ */
+var tries = 0;
+
+/**
+ * Max number of tries to watch for Cvox loading.
+ * @type {number}
+ */
+var MAX_TRIES = 15;
+
+/**
+ * Check for ChromeVox load.
+ * @param {Object} editor Editor to use.
+ */
+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
+ }
+});
- /* Initialize everything when ChromeVox has loaded. */
- watchForCvoxLoad();
});
diff --git a/lib/ace/ext/searchbox.js b/lib/ace/ext/searchbox.js
index c416b275..89fd1ddc 100644
--- a/lib/ace/ext/searchbox.js
+++ b/lib/ace/ext/searchbox.js
@@ -214,7 +214,9 @@ var SearchBox = function(editor, range, showReplaceForm) {
caseSensitive: this.caseSensitiveOption.checked,
wholeWord: this.wholeWordOption.checked
});
- dom.setCssClass(this.searchBox, "ace_nomatch", !range && this.searchInput.value);
+ var noMatch = !range && this.searchInput.value;
+ dom.setCssClass(this.searchBox, "ace_nomatch", noMatch);
+ this.editor._emit("findSearchBox", { match: !noMatch });
this.highlight();
};
this.findNext = function() {
diff --git a/lib/ace/ext/themelist_utils/themes.js b/lib/ace/ext/themelist_utils/themes.js
index 6b20770a..2e490c9f 100644
--- a/lib/ace/ext/themelist_utils/themes.js
+++ b/lib/ace/ext/themelist_utils/themes.js
@@ -16,8 +16,8 @@ module.exports.themes = [
"kr_theme",
"merbivore",
"merbivore_soft",
- "monokai",
"mono_industrial",
+ "monokai",
"pastel_on_dark",
"solarized_dark",
"solarized_light",