1/* 2 * jQuery Autocomplete plugin 1.1 3 * 4 * Copyright (c) 2009 Jörn Zaefferer 5 * 6 * Dual licensed under the MIT and GPL licenses: 7 * http://www.opensource.org/licenses/mit-license.php 8 * http://www.gnu.org/licenses/gpl.html 9 * 10 * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $ 11 */ 12 13;(function($) { 14 15$.fn.extend({ 16 autocomplete: function(urlOrData, options) { 17 var isUrl = typeof urlOrData == "string"; 18 options = $.extend({}, $.Autocompleter.defaults, { 19 url: isUrl ? urlOrData : null, 20 data: isUrl ? null : urlOrData, 21 delay: isUrl ? $.Autocompleter.defaults.delay : 10, 22 max: options && !options.scroll ? 10 : 150 23 }, options); 24 25 // if highlight is set to false, replace it with a do-nothing function 26 options.highlight = options.highlight || function(value) { return value; }; 27 28 // if the formatMatch option is not specified, then use formatItem for backwards compatibility 29 options.formatMatch = options.formatMatch || options.formatItem; 30 31 return this.each(function() { 32 new $.Autocompleter(this, options); 33 }); 34 }, 35 result: function(handler) { 36 return this.bind("result", handler); 37 }, 38 search: function(handler) { 39 return this.trigger("search", [handler]); 40 }, 41 flushCache: function() { 42 return this.trigger("flushCache"); 43 }, 44 setOptions: function(options){ 45 return this.trigger("setOptions", [options]); 46 }, 47 unautocomplete: function() { 48 return this.trigger("unautocomplete"); 49 } 50}); 51 52$.Autocompleter = function(input, options) { 53 54 var KEY = { 55 UP: 38, 56 DOWN: 40, 57 DEL: 46, 58 TAB: 9, 59 RETURN: 13, 60 ESC: 27, 61 COMMA: 191, 62 PAGEUP: 33, 63 PAGEDOWN: 34, 64 BACKSPACE: 8 65 }; 66 67 // Create $ object for input element 68 var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 69 70 var timeout; 71 var previousValue = ""; 72 var cache = $.Autocompleter.Cache(options); 73 var hasFocus = 0; 74 var lastKeyPressCode; 75 var config = { 76 mouseDownOnSelect: false 77 }; 78 var select = $.Autocompleter.Select(options, input, selectCurrent, config); 79 80 var blockSubmit; 81 82 // prevent form submit in opera when selecting with return key 83 $.browser.opera && $(input.form).bind("submit.autocomplete", function() { 84 if (blockSubmit) { 85 blockSubmit = false; 86 return false; 87 } 88 }); 89 90 // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all 91 $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { 92 // a keypress means the input has focus 93 // avoids issue where input had focus before the autocomplete was applied 94 hasFocus = 1; 95 // track last key pressed 96 lastKeyPressCode = event.keyCode; 97 switch(event.keyCode) { 98 99 case KEY.UP: 100 event.preventDefault(); 101 if ( select.visible() ) { 102 select.prev(); 103 } else { 104 onChange(0, true); 105 } 106 break; 107 108 case KEY.DOWN: 109 event.preventDefault(); 110 if ( select.visible() ) { 111 select.next(); 112 } else { 113 onChange(0, true); 114 } 115 break; 116 117 case KEY.PAGEUP: 118 event.preventDefault(); 119 if ( select.visible() ) { 120 select.pageUp(); 121 } else { 122 onChange(0, true); 123 } 124 break; 125 126 case KEY.PAGEDOWN: 127 event.preventDefault(); 128 if ( select.visible() ) { 129 select.pageDown(); 130 } else { 131 onChange(0, true); 132 } 133 break; 134 135 // matches also semicolon 136 // ÐÑклÑÑаем некоÑÑекÑнÑÑ ÑеакÑÐ¸Ñ Ð½Ð° запÑÑÑÑ 137 //case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 138 case KEY.TAB: 139 case KEY.RETURN: 140 if( selectCurrent() ) { 141 // stop default to prevent a form submit, Opera needs special handling 142 event.preventDefault(); 143 blockSubmit = true; 144 return false; 145 } 146 break; 147 148 case KEY.ESC: 149 select.hide(); 150 break; 151 152 default: 153 clearTimeout(timeout); 154 timeout = setTimeout(onChange, options.delay); 155 break; 156 } 157 }).focus(function(){ 158 // track whether the field has focus, we shouldn't process any 159 // results if the field no longer has focus 160 hasFocus++; 161 }).blur(function() { 162 hasFocus = 0; 163 if (!config.mouseDownOnSelect) { 164 hideResults(); 165 } 166 }).click(function() { 167 // show select when clicking in a focused field 168 if ( hasFocus++ > 1 && !select.visible() ) { 169 onChange(0, true); 170 } 171 }).bind("search", function() { 172 // TODO why not just specifying both arguments? 173 var fn = (arguments.length > 1) ? arguments[1] : null; 174 function findValueCallback(q, data) { 175 var result; 176 if( data && data.length ) { 177 for (var i=0; i < data.length; i++) { 178 if( data[i].result.toLowerCase() == q.toLowerCase() ) { 179 result = data[i]; 180 break; 181 } 182 } 183 } 184 if( typeof fn == "function" ) fn(result); 185 else $input.trigger("result", result && [result.data, result.value]); 186 } 187 $.each(trimWords($input.val()), function(i, value) { 188 request(value, findValueCallback, findValueCallback); 189 }); 190 }).bind("flushCache", function() { 191 cache.flush(); 192 }).bind("setOptions", function() { 193 $.extend(options, arguments[1]); 194 // if we've updated the data, repopulate 195 if ( "data" in arguments[1] ) 196 cache.populate(); 197 }).bind("unautocomplete", function() { 198 select.unbind(); 199 $input.unbind(); 200 $(input.form).unbind(".autocomplete"); 201 }); 202 203 204 function selectCurrent() { 205 var selected = select.selected(); 206 if( !selected ) 207 return false;
208 209 var v = selected.result; 210 previousValue = v; 211 212 if ( options.multiple ) { 213 var words = trimWords($input.val()); 214 if ( words.length > 1 ) { 215 var seperator = options.multipleSeparator.length; 216 var cursorAt = $(input).selection().start; 217 var wordAt, progress = 0; 218 // Ð¦ÐµÐ»Ñ Ð½Ð°ÑÑоÑÑего Ñикла -- вÑÑÑниÑÑ ÐºÐ°ÐºÐ¾Ðµ по поÑÑÐ´ÐºÑ Ñлово нÑждаеÑÑÑ Ð² авÑозамене 219 $.each(words, function(i, word) { 220 progress += word.length; 221 // Ð Ñом ÑлÑÑае еÑли длинна вÑÐµÑ Ñлов болÑÑе или Ñавна Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÐºÑÑÑоÑа, ÑвелиÑиваем wordAt(Ð½Ð¾Ð¼ÐµÑ Ð·Ð°Ð¼ÐµÐ½Ñемого Ñлова) и пÑекÑаÑаем вÑполнение Ñикла. 222 //alert("cursorAt"+cursorAt); 223 //alert("progress"+progress); 224 if (cursorAt <= progress) { 225 wordAt = i; 226 return false; 227 } 228 progress += seperator; 229 }); 230 if (cursorAt<=1) wordAt=words.length-1; 231 words[wordAt] = v; 232 // TODO this should set the cursor to the right position, but it gets overriden somewhere 233 //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); 234 //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); 235 v = words.join( options.multipleSeparator ); 236 } 237 v += options.multipleSeparator; 238 } 239 240 //if (cursorAt<2) cursorAt=v.length; 241 //alert(cursorAt); 242 $input.val(v); 243 hideResultsNow(); 244 $input.trigger("result", [selected.data, selected.value]); 245 return true; 246 } 247 248 function onChange(crap, skipPrevCheck) { 249 if( lastKeyPressCode == KEY.DEL ) { 250 select.hide(); 251 return; 252 } 253 254 var currentValue = $input.val(); 255 256 if ( !skipPrevCheck && currentValue == previousValue ) 257 return; 258 259 previousValue = currentValue; 260 // ÐÑÐ¾Ð±Ð»ÐµÐ¼Ñ Ð½Ñжно оÑÑлеживаÑÑ Ð·Ð´ÐµÑÑ 261 currentValue = lastWord(currentValue); 262 if ( currentValue.length >= options.minChars) { 263 $input.addClass(options.loadingClass); 264 if (!options.matchCase) 265 currentValue = currentValue.toLowerCase(); 266 request(currentValue, receiveData, hideResultsNow); 267 } else { 268 stopLoading(); 269 select.hide(); 270 } 271 }; 272 273 function trimWords(value) { 274 if (!value) 275 return [""]; 276 if (!options.multiple) 277 return [$.trim(value)]; 278 return $.map(value.split(options.multipleSeparator), function(word) { 279 return $.trim(value).length ? $.trim(word) : null; 280 }); 281 } 282 283 function lastWord(value) { 284 if ( !options.multiple ) 285 return value; 286 var words = trimWords(value); 287 if (words.length == 1) 288 return words[0]; 289 var cursorAt = $(input).selection().start; 290 if (cursorAt == value.length) { 291 words = trimWords(value) 292 } else { 293 words = trimWords(value.replace(value.substring(cursorAt), "")); 294 } 295 return words[words.length - 1]; 296 } 297 298 // fills in the input box w/the first match (assumed to be the best match) 299 // q: the term entered 300 // sValue: the first matching result 301 function autoFill(q, sValue){ 302 // autofill in the complete box w/the first match as long as the user hasn't entered in more data 303 // if the last user key pressed was backspace, don't autofill 304 if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { 305 // fill in the value (keep the case the user has typed) 306 $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 307 // select the portion of the value not typed by the user (so the next character will erase) 308 $(input).selection(previousValue.length, previousValue.length + sValue.length); 309 } 310 }; 311 312 function hideResults() { 313 clearTimeout(timeout); 314 timeout = setTimeout(hideResultsNow, 200); 315 }; 316 317 function hideResultsNow() { 318 var wasVisible = select.visible(); 319 select.hide(); 320 clearTimeout(timeout); 321 stopLoading(); 322 if (options.mustMatch) { 323 // call search and run callback 324 $input.search( 325 function (result){ 326 // if no value found, clear the input box 327 if( !result ) { 328 if (options.multiple) { 329 var words = trimWords($input.val()).slice(0, -1); 330 $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); 331 } 332 else { 333 $input.val( "" ); 334 $input.trigger("result", null); 335 } 336 } 337 } 338 ); 339 } 340 }; 341 342 function receiveData(q, data) { 343 if ( data && data.length && hasFocus ) { 344 stopLoading(); 345 select.display(data, q); 346 autoFill(q, data[0].value); 347 select.show(); 348 } else { 349 hideResultsNow(); 350 } 351 }; 352 353 function request(term, success, failure) { 354 if (!options.matchCase) 355 term = term.toLowerCase(); 356 var data = cache.load(term); 357 // recieve the cached data 358 if (data && data.length) { 359 success(term, data); 360 // if an AJAX url has been supplied, try loading the data now 361 } else if( (typeof options.url == "string") && (options.url.length > 0) ){ 362 363 var extraParams = { 364 timestamp: +new Date() 365 }; 366 $.each(options.extraParams, function(key, param) { 367 extraParams[key] = typeof param == "function" ? param() : param; 368 }); 369 370 $.ajax({ 371 // try to leverage ajaxQueue plugin to abort previous requests 372 mode: "abort", 373 // limit abortion to this input 374 port: "autocomplete" + input.name, 375 dataType: options.dataType, 376 url: options.url, 377 data: $.extend({ 378 q: lastWord(term), 379 limit: options.max 380 }, extraParams), 381 success: function(data) { 382 var parsed = options.parse && options.parse(data) || parse(data); 383 cache.add(term, parsed); 384 success(term, parsed); 385 } 386 }); 387 } else { 388 // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match 389 select.emptyList(); 390 failure(term); 391 } 392 }; 393 394 function parse(data) { 395 var parsed = []; 396 var rows = data.split("\n"); 397 for (var i=0; i < rows.length; i++) { 398 var row = $.trim(rows[i]); 399 if (row) { 400 row = row.split("|"); 401 parsed[parsed.length] = { 402 data: row, 403 value: row[0], 404 result: options.formatResult && options.formatResult(row, row[0]) || row[0] 405 }; 406 } 407 } 408 return parsed; 409 }; 410 411 function stopLoading() { 412 $input.removeClass(options.loadingClass); 413 }; 414 415}; 416 417$.Autocompleter.defaults = { 418 inputClass: "ac_input", 419 resultsClass: "ac_results", 420 loadingClass: "ac_loading", 421 minChars: 1, 422 delay: 400, 423 matchCase: false, 424 matchSubset: true,
425 matchContains: false, 426 cacheLength: 10, 427 max: 100, 428 mustMatch: false, 429 extraParams: {}, 430 selectFirst: true, 431 formatItem: function(row) { return row[0]; }, 432 formatMatch: null, 433 autoFill: false, 434 width: 0, 435 multiple: false, 436 multipleSeparator: ", ", 437 highlight: function(value, term) { 438 return value.replace(new RegExp("^(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")", "gi"), "<strong>$1</strong>"); 439 }, 440 scroll: true, 441 scrollHeight: 180 442}; 443 444$.Autocompleter.Cache = function(options) { 445 446 var data = {}; 447 var length = 0; 448 449 function matchSubset(s, sub) { 450 if (!options.matchCase) 451 s = s.toLowerCase(); 452 var i = s.indexOf(sub); 453 if (options.matchContains == "word"){ 454 i = s.toLowerCase().search("\\b" + sub.toLowerCase()); 455 } 456 if (i == -1) return false; 457 return i == 0 || options.matchContains; 458 }; 459 460 function add(q, value) { 461 if (length > options.cacheLength){ 462 flush(); 463 } 464 if (!data[q]){ 465 length++; 466 } 467 data[q] = value; 468 } 469 470 function populate(){ 471 if( !options.data ) return false; 472 // track the matches 473 var stMatchSets = {}, 474 nullData = 0; 475 476 // no url was specified, we need to adjust the cache length to make sure it fits the local data store 477 if( !options.url ) options.cacheLength = 1; 478 479 // track all options for minChars = 0 480 stMatchSets[""] = []; 481 482 // loop through the array and create a lookup structure 483 for ( var i = 0, ol = options.data.length; i < ol; i++ ) { 484 var rawValue = options.data[i]; 485 // if rawValue is a string, make an array otherwise just reference the array 486 rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 487 488 var value = options.formatMatch(rawValue, i+1, options.data.length); 489 if ( value === false ) 490 continue; 491 492 var firstChar = value.charAt(0).toLowerCase(); 493 // if no lookup array for this character exists, look it up now 494 if( !stMatchSets[firstChar] ) 495 stMatchSets[firstChar] = []; 496 497 // if the match is a string 498 var row = { 499 value: value, 500 data: rawValue, 501 result: options.formatResult && options.formatResult(rawValue) || value 502 }; 503 504 // push the current match into the set list 505 stMatchSets[firstChar].push(row); 506 507 // keep track of minChars zero items 508 if ( nullData++ < options.max ) { 509 stMatchSets[""].push(row); 510 } 511 }; 512 513 // add the data items to the cache 514 $.each(stMatchSets, function(i, value) { 515 // increase the cache size 516 options.cacheLength++; 517 // add to the cache 518 add(i, value); 519 }); 520 } 521 522 // populate any existing data 523 setTimeout(populate, 25); 524 525 function flush(){ 526 data = {}; 527 length = 0; 528 } 529 530 return { 531 flush: flush, 532 add: add, 533 populate: populate, 534 load: function(q) { 535 if (!options.cacheLength || !length) 536 return null; 537 /* 538 * if dealing w/local data and matchContains than we must make sure 539 * to loop through all the data collections looking for matches 540 */ 541 if( !options.url && options.matchContains ){ 542 // track all matches 543 var csub = []; 544 // loop through all the data grids for matches 545 for( var k in data ){ 546 // don't search through the stMatchSets[""] (minChars: 0) cache 547 // this prevents duplicates 548 if( k.length > 0 ){ 549 var c = data[k]; 550 $.each(c, function(i, x) { 551 // if we've got a match, add it to the array 552 if (matchSubset(x.value, q)) { 553 csub.push(x); 554 } 555 }); 556 } 557 } 558 return csub; 559 } else 560 // if the exact item exists, use it 561 if (data[q]){ 562 return data[q]; 563 } else 564 if (options.matchSubset) { 565 for (var i = q.length - 1; i >= options.minChars; i--) { 566 var c = data[q.substr(0, i)]; 567 if (c) { 568 var csub = []; 569 $.each(c, function(i, x) { 570 if (matchSubset(x.value, q)) { 571 csub[csub.length] = x; 572 } 573 }); 574 return csub; 575 } 576 } 577 } 578 return null; 579 } 580 }; 581}; 582 583$.Autocompleter.Select = function (options, input, select, config) { 584 var CLASSES = { 585 ACTIVE: "ac_over" 586 }; 587 588 var listItems, 589 active = -1, 590 data, 591 term = "", 592 needsInit = true, 593 element, 594 list; 595 596 // Create results 597 function init() { 598 if (!needsInit) 599 return; 600 element = $("<div/>") 601 .hide() 602 .addClass(options.resultsClass) 603 .css("position", "absolute") 604 .appendTo(document.body); 605 606 list = $("<ul/>").appendTo(element).mouseover( function(event) { 607 if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { 608 active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); 609 $(target(event)).addClass(CLASSES.ACTIVE); 610 } 611 }).click(function(event) { 612 $(target(event)).addClass(CLASSES.ACTIVE); 613 select(); 614 // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus 615 input.focus(); 616 return false;
617 }).mousedown(function() { 618 config.mouseDownOnSelect = true; 619 }).mouseup(function() { 620 config.mouseDownOnSelect = false; 621 }); 622 623 if( options.width > 0 ) 624 element.css("width", options.width); 625 626 needsInit = false; 627 } 628 629 function target(event) { 630 var element = event.target; 631 while(element && element.tagName != "LI") 632 element = element.parentNode; 633 // more fun with IE, sometimes event.target is empty, just ignore it then 634 if(!element) 635 return []; 636 return element; 637 } 638 639 function moveSelect(step) { 640 listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); 641 movePosition(step); 642 var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); 643 if(options.scroll) { 644 var offset = 0; 645 listItems.slice(0, active).each(function() { 646 offset += this.offsetHeight; 647 }); 648 if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { 649 list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); 650 } else if(offset < list.scrollTop()) { 651 list.scrollTop(offset); 652 } 653 } 654 }; 655 656 function movePosition(step) { 657 active += step; 658 if (active < 0) { 659 active = listItems.size() - 1; 660 } else if (active >= listItems.size()) { 661 active = 0; 662 } 663 } 664 665 function limitNumberOfItems(available) { 666 return options.max && options.max < available 667 ? options.max 668 : available; 669 } 670 671 function fillList() { 672 list.empty(); 673 var max = limitNumberOfItems(data.length); 674 for (var i=0; i < max; i++) { 675 if (!data[i]) 676 continue; 677 var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); 678 if ( formatted === false ) 679 continue; 680 var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; 681 $.data(li, "ac_data", data[i]); 682 } 683 listItems = list.find("li"); 684 if ( options.selectFirst ) { 685 listItems.slice(0, 1).addClass(CLASSES.ACTIVE); 686 active = 0; 687 } 688 // apply bgiframe if available 689 if ( $.fn.bgiframe ) 690 list.bgiframe(); 691 } 692 693 return { 694 display: function(d, q) { 695 init(); 696 data = d; 697 term = q; 698 fillList(); 699 }, 700 next: function() { 701 moveSelect(1); 702 }, 703 prev: function() { 704 moveSelect(-1); 705 }, 706 pageUp: function() { 707 if (active != 0 && active - 8 < 0) { 708 moveSelect( -active ); 709 } else { 710 moveSelect(-8); 711 } 712 }, 713 pageDown: function() { 714 if (active != listItems.size() - 1 && active + 8 > listItems.size()) { 715 moveSelect( listItems.size() - 1 - active ); 716 } else { 717 moveSelect(8); 718 } 719 }, 720 hide: function() { 721 element && element.hide(); 722 listItems && listItems.removeClass(CLASSES.ACTIVE); 723 active = -1; 724 }, 725 visible : function() { 726 return element && element.is(":visible"); 727 }, 728 current: function() { 729 return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); 730 }, 731 show: function() { 732 var offset = $(input).offset(); 733 element.css({ 734 width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), 735 top: offset.top + input.offsetHeight, 736 left: offset.left 737 }).show(); 738 if(options.scroll) { 739 list.scrollTop(0); 740 list.css({ 741 maxHeight: options.scrollHeight, 742 overflow: 'auto' 743 }); 744 745 if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { 746 var listHeight = 0; 747 listItems.each(function() { 748 listHeight += this.offsetHeight; 749 }); 750 var scrollbarsVisible = listHeight > options.scrollHeight; 751 list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); 752 if (!scrollbarsVisible) { 753 // IE doesn't recalculate width when scrollbar disappears 754 listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); 755 } 756 } 757 758 } 759 }, 760 selected: function() { 761 var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); 762 return selected && selected.length && $.data(selected[0], "ac_data"); 763 }, 764 emptyList: function (){ 765 list && list.empty(); 766 }, 767 unbind: function() { 768 element && element.remove(); 769 } 770 }; 771}; 772 773$.fn.selection = function(start, end) { 774 if (start !== undefined) { 775 return this.each(function() { 776 if( this.createTextRange ){ 777 var selRange = this.createTextRange(); 778 if (end === undefined || start == end) { 779 selRange.move("character", start); 780 selRange.select(); 781 } else { 782 selRange.collapse(true); 783 selRange.moveStart("character", start); 784 selRange.moveEnd("character", end); 785 selRange.select(); 786 } 787 } else if( this.setSelectionRange ){ 788 this.setSelectionRange(start, end); 789 } else if( this.selectionStart ){ 790 this.selectionStart = start; 791 this.selectionEnd = end; 792 } 793 }); 794 } 795 var field = this[0]; 796 if ( field.createTextRange ) { 797 var range = document.selection.createRange(), 798 orig = field.value, 799 teststring = "<->", 800 textLength = range.text.length; 801 range.text = teststring; 802 var caretAt = field.value.indexOf(teststring); 803 field.value = orig; 804 this.selection(caretAt, caretAt + textLength); 805 return { 806 start: caretAt, 807 end: caretAt + textLength 808 } 809 } else if( field.selectionStart !== undefined ){ 810 return { 811 start: field.selectionStart, 812 end: field.selectionEnd 813 } 814 } 815}; 816 817}
817)(jQuery);
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.