corrade-nucleus-nucleons – Blame information for rev 20

Subversion Repositories:
Rev:
Rev Author Line No. Line
20 office 1 ace.define("ace/ext/chromevox",["require","exports","module","ace/editor","ace/config"], function(require, exports, module) {
2 var cvoxAce = {};
3 cvoxAce.SpeechProperty;
4 cvoxAce.Cursor;
5 cvoxAce.Token;
6 cvoxAce.Annotation;
7 var CONSTANT_PROP = {
8 'rate': 0.8,
9 'pitch': 0.4,
10 'volume': 0.9
11 };
12 var DEFAULT_PROP = {
13 'rate': 1,
14 'pitch': 0.5,
15 'volume': 0.9
16 };
17 var ENTITY_PROP = {
18 'rate': 0.8,
19 'pitch': 0.8,
20 'volume': 0.9
21 };
22 var KEYWORD_PROP = {
23 'rate': 0.8,
24 'pitch': 0.3,
25 'volume': 0.9
26 };
27 var STORAGE_PROP = {
28 'rate': 0.8,
29 'pitch': 0.7,
30 'volume': 0.9
31 };
32 var VARIABLE_PROP = {
33 'rate': 0.8,
34 'pitch': 0.8,
35 'volume': 0.9
36 };
37 var DELETED_PROP = {
38 'punctuationEcho': 'none',
39 'relativePitch': -0.6
40 };
41 var ERROR_EARCON = 'ALERT_NONMODAL';
42 var MODE_SWITCH_EARCON = 'ALERT_MODAL';
43 var NO_MATCH_EARCON = 'INVALID_KEYPRESS';
44 var INSERT_MODE_STATE = 'insertMode';
45 var COMMAND_MODE_STATE = 'start';
46  
47 var REPLACE_LIST = [
48 {
49 substr: ';',
50 newSubstr: ' semicolon '
51 },
52 {
53 substr: ':',
54 newSubstr: ' colon '
55 }
56 ];
57 var Command = {
58 SPEAK_ANNOT: 'annots',
59 SPEAK_ALL_ANNOTS: 'all_annots',
60 TOGGLE_LOCATION: 'toggle_location',
61 SPEAK_MODE: 'mode',
62 SPEAK_ROW_COL: 'row_col',
63 TOGGLE_DISPLACEMENT: 'toggle_displacement',
64 FOCUS_TEXT: 'focus_text'
65 };
66 var KEY_PREFIX = 'CONTROL + SHIFT ';
67 cvoxAce.editor = null;
68 var lastCursor = null;
69 var annotTable = {};
70 var shouldSpeakRowLocation = false;
71 var shouldSpeakDisplacement = false;
72 var changed = false;
73 var vimState = null;
74 var keyCodeToShortcutMap = {};
75 var cmdToShortcutMap = {};
76 var getKeyShortcutString = function(keyCode) {
77 return KEY_PREFIX + String.fromCharCode(keyCode);
78 };
79 var isVimMode = function() {
80 var keyboardHandler = cvoxAce.editor.keyBinding.getKeyboardHandler();
81 return keyboardHandler.$id === 'ace/keyboard/vim';
82 };
83 var getCurrentToken = function(cursor) {
84 return cvoxAce.editor.getSession().getTokenAt(cursor.row, cursor.column + 1);
85 };
86 var getCurrentLine = function(cursor) {
87 return cvoxAce.editor.getSession().getLine(cursor.row);
88 };
89 var onRowChange = function(currCursor) {
90 if (annotTable[currCursor.row]) {
91 cvox.Api.playEarcon(ERROR_EARCON);
92 }
93 if (shouldSpeakRowLocation) {
94 cvox.Api.stop();
95 speakChar(currCursor);
96 speakTokenQueue(getCurrentToken(currCursor));
97 speakLine(currCursor.row, 1);
98 } else {
99 speakLine(currCursor.row, 0);
100 }
101 };
102 var isWord = function(cursor) {
103 var line = getCurrentLine(cursor);
104 var lineSuffix = line.substr(cursor.column - 1);
105 if (cursor.column === 0) {
106 lineSuffix = ' ' + line;
107 }
108 var firstWordRegExp = /^\W(\w+)/;
109 var words = firstWordRegExp.exec(lineSuffix);
110 return words !== null;
111 };
112 var rules = {
113 'constant': {
114 prop: CONSTANT_PROP
115 },
116 'entity': {
117 prop: ENTITY_PROP
118 },
119 'keyword': {
120 prop: KEYWORD_PROP
121 },
122 'storage': {
123 prop: STORAGE_PROP
124 },
125 'variable': {
126 prop: VARIABLE_PROP
127 },
128 'meta': {
129 prop: DEFAULT_PROP,
130 replace: [
131 {
132 substr: '</',
133 newSubstr: ' closing tag '
134 },
135 {
136 substr: '/>',
137 newSubstr: ' close tag '
138 },
139 {
140 substr: '<',
141 newSubstr: ' tag start '
142 },
143 {
144 substr: '>',
145 newSubstr: ' tag end '
146 }
147 ]
148 }
149 };
150 var DEFAULT_RULE = {
151 prop: DEFAULT_RULE
152 };
153 var expand = function(value, replaceRules) {
154 var newValue = value;
155 for (var i = 0; i < replaceRules.length; i++) {
156 var replaceRule = replaceRules[i];
157 var regexp = new RegExp(replaceRule.substr, 'g');
158 newValue = newValue.replace(regexp, replaceRule.newSubstr);
159 }
160 return newValue;
161 };
162 var mergeTokens = function(tokens, start, end) {
163 var newToken = {};
164 newToken.value = '';
165 newToken.type = tokens[start].type;
166 for (var j = start; j < end; j++) {
167 newToken.value += tokens[j].value;
168 }
169 return newToken;
170 };
171 var mergeLikeTokens = function(tokens) {
172 if (tokens.length <= 1) {
173 return tokens;
174 }
175 var newTokens = [];
176 var lastLikeIndex = 0;
177 for (var i = 1; i < tokens.length; i++) {
178 var lastLikeToken = tokens[lastLikeIndex];
179 var currToken = tokens[i];
180 if (getTokenRule(lastLikeToken) !== getTokenRule(currToken)) {
181 newTokens.push(mergeTokens(tokens, lastLikeIndex, i));
182 lastLikeIndex = i;
183 }
184 }
185 newTokens.push(mergeTokens(tokens, lastLikeIndex, tokens.length));
186 return newTokens;
187 };
188 var isRowWhiteSpace = function(row) {
189 var line = cvoxAce.editor.getSession().getLine(row);
190 var whiteSpaceRegexp = /^\s*$/;
191 return whiteSpaceRegexp.exec(line) !== null;
192 };
193 var speakLine = function(row, queue) {
194 var tokens = cvoxAce.editor.getSession().getTokens(row);
195 if (tokens.length === 0 || isRowWhiteSpace(row)) {
196 cvox.Api.playEarcon('EDITABLE_TEXT');
197 return;
198 }
199 tokens = mergeLikeTokens(tokens);
200 var firstToken = tokens[0];
201 tokens = tokens.filter(function(token) {
202 return token !== firstToken;
203 });
204 speakToken_(firstToken, queue);
205 tokens.forEach(speakTokenQueue);
206 };
207 var speakTokenFlush = function(token) {
208 speakToken_(token, 0);
209 };
210 var speakTokenQueue = function(token) {
211 speakToken_(token, 1);
212 };
213 var getTokenRule = function(token) {
214 if (!token || !token.type) {
215 return;
216 }
217 var split = token.type.split('.');
218 if (split.length === 0) {
219 return;
220 }
221 var type = split[0];
222 var rule = rules[type];
223 if (!rule) {
224 return DEFAULT_RULE;
225 }
226 return rule;
227 };
228 var speakToken_ = function(token, queue) {
229 var rule = getTokenRule(token);
230 var value = expand(token.value, REPLACE_LIST);
231 if (rule.replace) {
232 value = expand(value, rule.replace);
233 }
234 cvox.Api.speak(value, queue, rule.prop);
235 };
236 var speakChar = function(cursor) {
237 var line = getCurrentLine(cursor);
238 cvox.Api.speak(line[cursor.column], 1);
239 };
240 var speakDisplacement = function(lastCursor, currCursor) {
241 var line = getCurrentLine(currCursor);
242 var displace = line.substring(lastCursor.column, currCursor.column);
243 displace = displace.replace(/ /g, ' space ');
244 cvox.Api.speak(displace);
245 };
246 var speakCharOrWordOrLine = function(lastCursor, currCursor) {
247 if (Math.abs(lastCursor.column - currCursor.column) !== 1) {
248 var currLineLength = getCurrentLine(currCursor).length;
249 if (currCursor.column === 0 || currCursor.column === currLineLength) {
250 speakLine(currCursor.row, 0);
251 return;
252 }
253 if (isWord(currCursor)) {
254 cvox.Api.stop();
255 speakTokenQueue(getCurrentToken(currCursor));
256 return;
257 }
258 }
259 speakChar(currCursor);
260 };
261 var onColumnChange = function(lastCursor, currCursor) {
262 if (!cvoxAce.editor.selection.isEmpty()) {
263 speakDisplacement(lastCursor, currCursor);
264 cvox.Api.speak('selected', 1);
265 }
266 else if (shouldSpeakDisplacement) {
267 speakDisplacement(lastCursor, currCursor);
268 } else {
269 speakCharOrWordOrLine(lastCursor, currCursor);
270 }
271 };
272 var onCursorChange = function(evt) {
273 if (changed) {
274 changed = false;
275 return;
276 }
277 var currCursor = cvoxAce.editor.selection.getCursor();
278 if (currCursor.row !== lastCursor.row) {
279 onRowChange(currCursor);
280 } else {
281 onColumnChange(lastCursor, currCursor);
282 }
283 lastCursor = currCursor;
284 };
285 var onSelectionChange = function(evt) {
286 if (cvoxAce.editor.selection.isEmpty()) {
287 cvox.Api.speak('unselected');
288 }
289 };
290 var onChange = function(delta) {
291 switch (delta.action) {
292 case 'remove':
293 cvox.Api.speak(delta.text, 0, DELETED_PROP);
294 changed = true;
295 break;
296 case 'insert':
297 cvox.Api.speak(delta.text, 0);
298 changed = true;
299 break;
300 }
301 };
302 var isNewAnnotation = function(annot) {
303 var row = annot.row;
304 var col = annot.column;
305 return !annotTable[row] || !annotTable[row][col];
306 };
307 var populateAnnotations = function(annotations) {
308 annotTable = {};
309 for (var i = 0; i < annotations.length; i++) {
310 var annotation = annotations[i];
311 var row = annotation.row;
312 var col = annotation.column;
313 if (!annotTable[row]) {
314 annotTable[row] = {};
315 }
316 annotTable[row][col] = annotation;
317 }
318 };
319 var onAnnotationChange = function(evt) {
320 var annotations = cvoxAce.editor.getSession().getAnnotations();
321 var newAnnotations = annotations.filter(isNewAnnotation);
322 if (newAnnotations.length > 0) {
323 cvox.Api.playEarcon(ERROR_EARCON);
324 }
325 populateAnnotations(annotations);
326 };
327 var speakAnnot = function(annot) {
328 var annotText = annot.type + ' ' + annot.text + ' on ' +
329 rowColToString(annot.row, annot.column);
330 annotText = annotText.replace(';', 'semicolon');
331 cvox.Api.speak(annotText, 1);
332 };
333 var speakAnnotsByRow = function(row) {
334 var annots = annotTable[row];
335 for (var col in annots) {
336 speakAnnot(annots[col]);
337 }
338 };
339 var rowColToString = function(row, col) {
340 return 'row ' + (row + 1) + ' column ' + (col + 1);
341 };
342 var speakCurrRowAndCol = function() {
343 cvox.Api.speak(rowColToString(lastCursor.row, lastCursor.column));
344 };
345 var speakAllAnnots = function() {
346 for (var row in annotTable) {
347 speakAnnotsByRow(row);
348 }
349 };
350 var speakMode = function() {
351 if (!isVimMode()) {
352 return;
353 }
354 switch (cvoxAce.editor.keyBinding.$data.state) {
355 case INSERT_MODE_STATE:
356 cvox.Api.speak('Insert mode');
357 break;
358 case COMMAND_MODE_STATE:
359 cvox.Api.speak('Command mode');
360 break;
361 }
362 };
363 var toggleSpeakRowLocation = function() {
364 shouldSpeakRowLocation = !shouldSpeakRowLocation;
365 if (shouldSpeakRowLocation) {
366 cvox.Api.speak('Speak location on row change enabled.');
367 } else {
368 cvox.Api.speak('Speak location on row change disabled.');
369 }
370 };
371 var toggleSpeakDisplacement = function() {
372 shouldSpeakDisplacement = !shouldSpeakDisplacement;
373 if (shouldSpeakDisplacement) {
374 cvox.Api.speak('Speak displacement on column changes.');
375 } else {
376 cvox.Api.speak('Speak current character or word on column changes.');
377 }
378 };
379 var onKeyDown = function(evt) {
380 if (evt.ctrlKey && evt.shiftKey) {
381 var shortcut = keyCodeToShortcutMap[evt.keyCode];
382 if (shortcut) {
383 shortcut.func();
384 }
385 }
386 };
387 var onChangeStatus = function(evt, editor) {
388 if (!isVimMode()) {
389 return;
390 }
391 var state = editor.keyBinding.$data.state;
392 if (state === vimState) {
393 return;
394 }
395 switch (state) {
396 case INSERT_MODE_STATE:
397 cvox.Api.playEarcon(MODE_SWITCH_EARCON);
398 cvox.Api.setKeyEcho(true);
399 break;
400 case COMMAND_MODE_STATE:
401 cvox.Api.playEarcon(MODE_SWITCH_EARCON);
402 cvox.Api.setKeyEcho(false);
403 break;
404 }
405 vimState = state;
406 };
407 var contextMenuHandler = function(evt) {
408 var cmd = evt.detail['customCommand'];
409 var shortcut = cmdToShortcutMap[cmd];
410 if (shortcut) {
411 shortcut.func();
412 cvoxAce.editor.focus();
413 }
414 };
415 var initContextMenu = function() {
416 var ACTIONS = SHORTCUTS.map(function(shortcut) {
417 return {
418 desc: shortcut.desc + getKeyShortcutString(shortcut.keyCode),
419 cmd: shortcut.cmd
420 };
421 });
422 var body = document.querySelector('body');
423 body.setAttribute('contextMenuActions', JSON.stringify(ACTIONS));
424 body.addEventListener('ATCustomEvent', contextMenuHandler, true);
425 };
426 var onFindSearchbox = function(evt) {
427 if (evt.match) {
428 speakLine(lastCursor.row, 0);
429 } else {
430 cvox.Api.playEarcon(NO_MATCH_EARCON);
431 }
432 };
433 var focus = function() {
434 cvoxAce.editor.focus();
435 };
436 var SHORTCUTS = [
437 {
438 keyCode: 49,
439 func: function() {
440 speakAnnotsByRow(lastCursor.row);
441 },
442 cmd: Command.SPEAK_ANNOT,
443 desc: 'Speak annotations on line'
444 },
445 {
446 keyCode: 50,
447 func: speakAllAnnots,
448 cmd: Command.SPEAK_ALL_ANNOTS,
449 desc: 'Speak all annotations'
450 },
451 {
452 keyCode: 51,
453 func: speakMode,
454 cmd: Command.SPEAK_MODE,
455 desc: 'Speak Vim mode'
456 },
457 {
458 keyCode: 52,
459 func: toggleSpeakRowLocation,
460 cmd: Command.TOGGLE_LOCATION,
461 desc: 'Toggle speak row location'
462 },
463 {
464 keyCode: 53,
465 func: speakCurrRowAndCol,
466 cmd: Command.SPEAK_ROW_COL,
467 desc: 'Speak row and column'
468 },
469 {
470 keyCode: 54,
471 func: toggleSpeakDisplacement,
472 cmd: Command.TOGGLE_DISPLACEMENT,
473 desc: 'Toggle speak displacement'
474 },
475 {
476 keyCode: 55,
477 func: focus,
478 cmd: Command.FOCUS_TEXT,
479 desc: 'Focus text'
480 }
481 ];
482 var onFocus = function(_, editor) {
483 cvoxAce.editor = editor;
484 editor.getSession().selection.on('changeCursor', onCursorChange);
485 editor.getSession().selection.on('changeSelection', onSelectionChange);
486 editor.getSession().on('change', onChange);
487 editor.getSession().on('changeAnnotation', onAnnotationChange);
488 editor.on('changeStatus', onChangeStatus);
489 editor.on('findSearchBox', onFindSearchbox);
490 editor.container.addEventListener('keydown', onKeyDown);
491  
492 lastCursor = editor.selection.getCursor();
493 };
494 var init = function(editor) {
495 onFocus(null, editor);
496 SHORTCUTS.forEach(function(shortcut) {
497 keyCodeToShortcutMap[shortcut.keyCode] = shortcut;
498 cmdToShortcutMap[shortcut.cmd] = shortcut;
499 });
500  
501 editor.on('focus', onFocus);
502 if (isVimMode()) {
503 cvox.Api.setKeyEcho(false);
504 }
505 initContextMenu();
506 };
507 function cvoxApiExists() {
508 return (typeof(cvox) !== 'undefined') && cvox && cvox.Api;
509 }
510 var tries = 0;
511 var MAX_TRIES = 15;
512 function watchForCvoxLoad(editor) {
513 if (cvoxApiExists()) {
514 init(editor);
515 } else {
516 tries++;
517 if (tries >= MAX_TRIES) {
518 return;
519 }
520 window.setTimeout(watchForCvoxLoad, 500, editor);
521 }
522 }
523  
524 var Editor = require('../editor').Editor;
525 require('../config').defineOptions(Editor.prototype, 'editor', {
526 enableChromevoxEnhancements: {
527 set: function(val) {
528 if (val) {
529 watchForCvoxLoad(this);
530 }
531 },
532 value: true // turn it on by default or check for window.cvox
533 }
534 });
535  
536 });
537 (function() {
538 ace.require(["ace/ext/chromevox"], function() {});
539 })();
540