corrade-nucleus-nucleons – Blame information for rev 2
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
2 | 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 |