1(function () { 2 var lang = ace.require("ace/lib/lang"); 3 var langTools = ace.require("ace/ext/language_tools"); 4 5 var staticCompleter = { 6 getCompletions: function (editor, session, pos, prefix, callback) { 7 var comps = $('#' + editor.container.id).data('auto-complete-list'); 8 if (comps) { 9 var words = []; 10 Object.keys(comps).forEach(function (key) { 11 var comps_key = comps[key]; 12 if (!Array.isArray(comps[key])) { 13 comps_key = [comps_key]; 14 } 15 words = words.concat(comps_key.map(function (d) { 16 return { name: d, value: d, meta: key }; 17 })); 18 }); 19 callback(null, words); 20 } 21 } 22 }; 23 langTools.addCompleter(staticCompleter); 24 25 var rlangCompleter = { 26 identifierRegexps: [ 27 /[a-zA-Z_0-9\.\:\-\u00A2-\uFFFF]/, 28 ], 29 getCompletions: function(editor, session, pos, prefix, callback) { 30 var inputId = editor.container.id; 31 32 // TODO: consider dropping onInputChange hook when completer is disabled for performance 33 Shiny.onInputChange(inputId + '_shinyAce_hint', { 34 // TODO: add an option to disable full document parsing for performance 35 document: session.getValue(), 36 linebuffer: session.getLine(pos.row), 37 cursorPosition: pos, 38 // nonce causes autocomplete event to trigger 39 // on R side even if Ctrl-Space is pressed twice 40 // with the same linebuffer and cursorPosition 41 nonce: Math.random() 42 }); 43 44 // store callback for dynamic completion 45 $('#' + inputId).data('autoCompleteCallback', callback); 46 } 47 }; 48 langTools.addCompleter(rlangCompleter); 49 50 // behavior upon selection of an autocompletion 51 // I was expecting this to be able to be defined with the rlangCompleter, 52 // but it appears to only be able to be defined per-completion. 53 // Upon a completion callback, completions are mapped over and assigned this 54 // function for insertion behavior for each completion entry 55 function r_insertMatch(editor, data) { 56 if (editor.completer.completions.filterText) { 57 var ranges = editor.selection.getAllRanges(); 58 for (var i = 0, range; range = ranges[i]; i++) { 59 range.start.column -= editor.completer.completions.filterText.length; 60 editor.session.remove(range); 61 } 62 } 63 if (data.snippet) { 64 snippetManager.insertSnippet(editor, data.snippet); 65 } else { 66 // insert completion 67 var insertString = data.value || data; 68 editor.execCommand("insertstring", insertString); 69 70 // automatically clobber existing code 71 var cursor = editor.getCursorPosition(); 72 var remainder = editor.session.getLine(cursor.row).slice(cursor.column); 73 var re_match = remainder.match(/(^[a-zA-Z0-9._:]*)((?:\(\)?|\))?)/); 74 if (re_match && re_match[0].length) { 75 // remove word that we're clobbering 76 editor.getSession().getDocument().removeInLine( 77 cursor.row, cursor.column, cursor.column + re_match[1].length); 78 79 // if function call, delete parens and navigate into existing call 80 if (insertString.endsWith("()") && re_match[2].length) { 81 editor.getSession().getDocument().removeInLine( 82 cursor.row, cursor.column - re_match[2].length, cursor.column); 83 if (re_match[2].length > 1) editor.navigateRight(1); 84 } 85 86 } else if (insertString.endsWith("()")) { 87 // navigate backwards into ()'s for function completions 88 editor.navigateLeft(1); 89 } 90 91 } 92 }; 93 langTools.addCompleter(rlangCompleter); 94 95 function updateEditor(el, data) { 96 var editor = {}; 97 if (typeof $(el).data('aceEditor') !== 'undefined') 98 editor = $(el).data('aceEditor'); 99 else 100 editor = ace.edit(el); 101 102 if (data.hasOwnProperty('fontSize')) { 103 el.style.fontSize = data.fontSize + 'px'; 104 } 105 106 if (data.hasOwnProperty('theme')) { 107 editor.setTheme("ace/theme/" + data.theme); 108 } 109 110 if (data.hasOwnProperty('mode')) { 111 editor.getSession().setMode('ace/mode/' + data.mode); 112 } 113 114 if (data.hasOwnProperty('value')) { 115 editor.setValue(data.value, -1); 116 } 117 118 if (data.hasOwnProperty("selectionId")) { 119 editor.getSelection().on("changeSelection", function () { 120 Shiny.onInputChange(el.id + "_" + data.selectionId, editor.getCopyText()); 121 }); 122 } 123 124 if (data.hasOwnProperty("cursorId")) { 125 editor.getSelection().on("changeCursor", function () { 126 Shiny.onInputChange(el.id + "_" + data.cursorId, editor.selection.getCursor()); 127 }); 128 } 129 130 if (data.hasOwnProperty("hotkeys")) {
131 Object.keys(data.hotkeys).forEach(function (key) { 132 editor.commands.addCommand({ 133 name: key, 134 bindKey: data.hotkeys[key], 135 exec: function (editor) { 136 var selection = editor.session.getTextRange(); 137 var range = editor.selection.getRange(); 138 var imax = editor.session.getLength() - range.end.row; 139 var inputId = editor.container.id; 140 var shinyEvent = { 141 editorId: inputId, 142 selection: selection, 143 range: range, 144 randNum: Math.random() 145 }; 146 Shiny.onInputChange(inputId + "_" + key, shinyEvent); 147 }, // exec end 148 readOnly: true // false if this command should not apply in readOnly mode 149 }); //editor.addCommand end 150 }); // forEach end 151 } 152 153 if (data.hasOwnProperty("code_hotkeys")) { 154 // data.code_hotkeys[0] should indicate the code type (e.g., "r", "python", etc.) 155 // in the future, this could load js code to "jump" through code of that type 156 Object.keys(data.code_hotkeys[1]).forEach(function (key) { 157 editor.commands.addCommand({ 158 name: key, 159 bindKey: data.code_hotkeys[1][key], 160 exec: function (editor) { 161 var selection = editor.session.getTextRange(); 162 var range = editor.selection.getRange(); 163 var imax = editor.session.getLength() - range.end.row; 164 var inputId = editor.container.id; 165 var line = ""; 166 if (selection === "") { 167 line = code_jump(editor, range, imax); 168 } 169 var shinyEvent = { 170 editorId: inputId, 171 selection: selection, 172 range: range, 173 line: line, 174 randNum: Math.random() 175 }; 176 Shiny.onInputChange(inputId + "_" + key, shinyEvent); 177 }, // exec end 178 readOnly: true // false if this command should not apply in readOnly mode 179 }); //editor.addCommand end 180 }); // forEach end 181 } 182 183 if (data.hasOwnProperty("debounce")) { 184 $(el).data("debounce", data.debounce); 185 } 186 187 if (data.hasOwnProperty("vimKeyBinding") && data.vimKeyBinding === true) { 188 editor.setKeyboardHandler("ace/keyboard/vim"); 189 } 190 191 if (data.hasOwnProperty("showLineNumbers")) { 192 if (data.showLineNumbers === false) { 193 editor.renderer.setShowGutter(false); 194 } else if (data.showLineNumbers === true) { 195 editor.renderer.setShowGutter(true); 196 } 197 } 198 199 if (data.hasOwnProperty("highlightActiveLine") && data.highlightActiveLine === false) { 200 editor.setHighlightActiveLine(false); 201 } 202 203 if (data.hasOwnProperty('readOnly')) { 204 editor.setReadOnly(data.readOnly); 205 } 206 207 if (data.hasOwnProperty('wordWrap')) { 208 editor.getSession().setUseWrapMode(data.wordWrap); 209 } 210 211 if (data.hasOwnProperty("useSoftTabs")) { 212 editor.setOption("useSoftTabs", data.useSoftTabs); 213 } 214 215 if (data.hasOwnProperty("tabSize")) { 216 editor.setOption("tabSize", data.tabSize); 217 } 218 219 if (data.hasOwnProperty("showInvisibles")) { 220 editor.setOption("showInvisibles", data.showInvisibles); 221 } 222 223 if (data.hasOwnProperty("showPrintMargin")) { 224 editor.setOption("showPrintMargin", data.showPrintMargin); 225 } 226 227 if (data.hasOwnProperty('border')) { 228 var classes = ['acenormal', 'aceflash', 'acealert']; 229 $(el).removeClass(classes.join(' ')); 230 $(el).addClass(data.border); 231 } 232 233 if (data.annotations) { 234 editor.getSession().setAnnotations(data.annotations); 235 } 236 237 if (data.docTooltip) { 238 // { docHTML: "", docText: "" } 239 if (data.docTooltip.docHTML || data.docTooltip.docText) { 240 if (!editor.completer.tooltipNode) { 241 if (editor.__tooltipTimerCall) 242 editor.__tooltipTimerCall.cancel(); 243 244 editor.__tooltipTimerCall = lang.delayedCall( 245 function() { 246 if (this.completer.activated) 247 this.completer.showDocTooltip(data.docTooltip); 248 }.bind(editor), 1000); 249 250 editor.__tooltipTimerCall(); 251 } else { 252 editor.completer.showDocTooltip(data.docTooltip); 253 } 254 } 255 } 256 257 if (data.hasOwnProperty('autoComplete')) { 258 var value = data.autoComplete; 259 editor.setOption('enableLiveAutocompletion', value === 'live'); 260 editor.setOption('enableBasicAutocompletion', value !== 'disabled'); 261 } 262 263 if (data.hasOwnProperty('autoCompleters')) { 264 var completers = data.autoCompleters; 265 editor.completers = []; 266 if (completers) { 267 if (!Array.isArray(completers)) { 268 completers = [completers]; 269 }
270 completers.forEach(function (completer) { 271 switch (completer) { 272 case 'snippet': 273 completer = langTools.snippetCompleter; 274 break; 275 case 'text': 276 completer = langTools.textCompleter; 277 break; 278 case 'keyword': 279 completer = langTools.keyWordCompleter; 280 break; 281 case 'static': 282 completer = staticCompleter; 283 break; 284 case 'rlang': 285 completer = rlangCompleter; 286 // add a keybind for "smart" tab - using tab for both indentation 287 // and autocompletion triggering, specific to R object names 288 editor.commands.addCommand({ 289 name: 'rlang_smartTab', 290 bindKey: { win: 'Tab', mac: 'TAB' }, 291 multiSelectAction: 'forEach', 292 exec: function(editor) { 293 var selection = editor.session.getTextRange(); 294 var range = editor.selection.getRange(); 295 var imax = editor.session.getLength() - range.end.row; 296 297 //// use regular indent whenever cursor is anything but standard 298 if (selection !== '' || editor.inMultiSelectMode) { 299 editor.indent(); 300 301 // otherwise see if autocompletion should be triggered 302 } else { 303 var linebuffer = editor.session.getLine(range.start.row).slice(0, range.start.column); 304 305 // if at the start of an object name or function call, kick off autocompletion 306 if (/[a-zA-Z._][a-zA-Z0-9._:]*$/.test(linebuffer) || /[a-zA-Z0-9._]\([^)]*$/.test(linebuffer)) { 307 if (editor.completer) editor.completer.detach(); 308 editor.execCommand('startAutocomplete'); 309 310 // otherwise do an indentation 311 } else { 312 editor.indent(); 313 } 314 } 315 } 316 }); 317 break; 318 } 319 320 // to each completer, add getDocTooltip callback to R 321 if (!completer.hasOwnProperty("getDocTooltip")) { 322 completer.getDocTooltip = function(item) { 323 Shiny.onInputChange(data.id + '_shinyAce_tooltipItem', item); 324 }; 325 } 326 327 editor.completers.push(completer); 328 }); 329 } 330 } 331 332 if (data.hasOwnProperty('autoCompleteList')) { 333 $(el).data('auto-complete-list', data.autoCompleteList); 334 } 335 336 if (data.hasOwnProperty("setBehavioursEnabled") && data.setBehavioursEnabled === false) { 337 editor.setBehavioursEnabled(data.setBehavioursEnabled); 338 } 339 340 // if (data.hasOwnProperty("codeCompletions")) { 341 // var callback = $(el).data('autoCompleteCallback'); 342 // if (callback !== undefined) callback(null, data.codeCompletions); 343 // } 344 345 // this is a bit r-specific, perhaps there's a better way to only link it 346 // to the rlang completer? 347 if (data.hasOwnProperty("codeCompletions")) { 348 var callback = $(el).data('autoCompleteCallback'); 349 data.codeCompletions = data.codeCompletions.map(function(completion) { 350 if (typeof completion.completer == "string") { 351 switch (completion.completer) { 352 case 'rlang': 353 completion.completer = {}; 354 completion.completer.insertMatch = r_insertMatch; 355 break; 356 } 357 } 358 return completion; 359 }); 360 361 if (callback !== undefined) callback(null, data.codeCompletions); 362 } 363 364 if (data.hasOwnProperty("autoScrollEditorIntoView") && data.autoScrollEditorIntoView === true) { 365 editor.setOption("autoScrollEditorIntoView", true); 366 if (data.hasOwnProperty("maxLines")) { 367 editor.setOption("maxLines", data.maxLines); 368 } 369 if (data.hasOwnProperty("minLines")) { 370 editor.setOption("minLines", data.minLines); 371 } 372 } 373 374 if (data.hasOwnProperty('placeholder')) { 375 // adapted from https://stackoverflow.com/a/26700324/1974918 376
377 function update() { 378 var shouldShow = !editor.session.getValue().length; 379 var node = editor.renderer.emptyMessageNode; 380 if (!shouldShow && node) { 381 editor.renderer.scroller.removeChild(editor.renderer.emptyMessageNode); 382 editor.renderer.emptyMessageNode = null; 383 } else if (shouldShow && !node) { 384 node = editor.renderer.emptyMessageNode = document.createElement("div"); 385 node.textContent = data.placeholder; 386 node.className = "ace_emptyMessage"; 387 node.style.padding = "0 15px"; 388 node.style.position = "absolute"; 389 node.style.zIndex = 9; 390 node.style.opacity = 0.5; 391 editor.renderer.scroller.appendChild(node); 392 } 393 } 394 395 editor.on("input", update); 396 setTimeout(update, 100); 397 } 398 399 if (typeof $(el).data('aceEditor') == 'undefined') 400 $(el).data("aceEditor", editor); 401 402 }; 403 404 var shinyAceInputBinding = new Shiny.InputBinding(); 405 $.extend(shinyAceInputBinding, { 406 find: function(scope) { 407 return $(scope).find(".shiny-ace"); 408 }, 409 initialize: function (el) { 410 var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); 411 if (scriptData) { 412 var data = JSON.parse(scriptData.textContent); 413 updateEditor(el, data); 414 } 415 }, 416 getValue: function(el) { 417 return($(el).data('aceEditor').getValue()); 418 }, 419 setValue: function(el, value) { 420 //TODO 421 }, 422 subscribe: function(el, callback) { 423 var editor = $(el).data('aceEditor'); 424 425 $(el).data('aceChangeCallback', function(e) { 426 callback(true); 427 428 // always clear annotation at current line on edit 429 var cp = editor.getCursorPosition(); 430 editor.getSession().setAnnotations( 431 editor.getSession().getAnnotations().filter( 432 a => !(a.row === cp.row && a.type === 'error'))); 433 434 // only trigger syntax parsing if idle for some time 435 if (editor.__annotationTimerCall) 436 editor.__annotationTimerCall.cancel(); 437 438 editor.__annotationTimerCall = lang.delayedCall( 439 function() { 440 Shiny.onInputChange( 441 this.attr('id') + '_shinyAce_annotationTrigger',
442 Math.random()); 443 }.bind($(el)), 1000); 444 editor.__annotationTimerCall(); 445 }); 446 447 $(el).data('aceEditor').getSession().addEventListener("change", 448 $(el).data('aceChangeCallback') 449 ); 450 }, 451 unsubscribe: function(el) { 452 $(el).data('aceEditor').getSession().removeEventListener("change", 453 $(el).data('aceChangeCallback')); 454 }, 455 getRatePolicy: function(el){ 456 return ({policy: 'debounce', delay: $(el).data('debounce') || 1000 }); 457 } 458 }); 459 460 Shiny.inputBindings.register(shinyAceInputBinding); 461 462 Shiny.addCustomMessageHandler('shinyAce', function (data) { 463 var id = data.id; 464 var el = document.getElementById(data.id); 465 updateEditor(el, data); 466 }); 467 468 // Allow toggle of the search-replace box in Ace 469 // see https://github.com/ajaxorg/ace/issues/3552 470 var toggle_search_replace = ace.require("ace/ext/searchbox").SearchBox.prototype.$searchBarKb.bindKey("Ctrl-f|Command-f|Ctrl-H|Command-Option-F", function (sb) { 471 var isReplace = sb.isReplace = !sb.isReplace; 472 sb.replaceBox.style.display = isReplace ? "" : "none"; 473 sb[isReplace ? "replaceInput" : "searchInput"].focus(); 474 }); 475 476})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.