├── LICENSE ├── README.md ├── hints.js ├── main.js └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ole Kröger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuinter 2 | 3 | A brackets extension to get hints for selectors in jQuery,HTML and CSS. 4 | 5 | ## How to use 6 | 7 | in jQuery: 8 | 9 | ![](https://cloud.githubusercontent.com/assets/4931746/8521140/3fdace8a-23e1-11e5-85d6-72b378f1894f.gif) 10 | 11 | in HTML: 12 | 13 | ![](https://cloud.githubusercontent.com/assets/4931746/8521128/1226964a-23e1-11e5-80f2-eec705b7cee8.gif) 14 | 15 | in CSS: 16 | 17 | ![](https://cloud.githubusercontent.com/assets/4931746/8521065/8953f89e-23e0-11e5-8ea9-03f2d2b07ad1.gif) 18 | 19 | 20 | ## You're rich? 21 | You don't need a second yacht and have some coins to spend? Here I am :) 22 | 23 | [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=Wikunia&url=https://github.com/Wikunia/brackets-jQuinter&title=Brackets-jQuinter&language=javascript&tags=github&category=software) 24 | [![Donate! :3](https://www.paypalobjects.com/en_US/GB/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=B5VQXWCZXYN2W) 25 | 26 | ## Versions 27 | 28 | ### v0.3.0 29 | 30 | + it's possible to correct misspelling without starting from the beginning 31 | + case insensitive 32 | 33 | 34 | ### v0.2.0 35 | 36 | + huge performance improvements 37 | + cache all hints at the beginning and on save 38 | 39 | ## Next steps 40 | 41 | + support more awesome things 42 | + please open an issue if you miss something 43 | -------------------------------------------------------------------------------- /hints.js: -------------------------------------------------------------------------------- 1 | // ========================================================================= 2 | // Hint Manager 3 | // ========================================================================= 4 | define(function () { 5 | var JSUtils = brackets.getModule("language/JSUtils"); 6 | var PerfUtils = brackets.getModule("utils/PerfUtils"); 7 | var LanguageManager = brackets.getModule("language/LanguageManager"); 8 | var ProjectManager = brackets.getModule("project/ProjectManager"); 9 | var DocumentManager = brackets.getModule("document/DocumentManager"); 10 | var EditorManager = brackets.getModule("editor/EditorManager"); 11 | var MainViewManager = brackets.getModule("view/MainViewManager"); 12 | 13 | var REGEX_HASHINT_CSS_ID = /^[\t ]*\#/; 14 | var REGEX_HASHINT_CSS_CLASS = /^[\t ]*\./; 15 | 16 | var REGEX_HTML_CLASS = / class="([^"]+)"/ig; 17 | var REGEX_HTML_ID = / id="([^"]+)"/ig; 18 | var REGEX_DATA = / data-([_a-zA-Z]+[_a-zA-Z0-9-]*)/g; 19 | var REGEX_CSS_CLASS = /\.(-?[_a-zA-Z][_a-zA-Z0-9-]*)/g; 20 | var REGEX_CSS_ID = /\#(-?[_a-zA-Z][_a-zA-Z0-9-]*)/g; 21 | 22 | var REGEX_REVERSE_INSIDE = /^([_a-zA-Z0-9-]*[_a-zA-Z]-?)(\#|\.)?['"]/; 23 | 24 | var LANGUAGES = {'javascript': ["javascript", "coffeescript", "livescript"], 25 | 'css': ["css", "less", "sass", "scss"], 26 | 'html': ["html","mustache"]}; 27 | var CHECK_LANGUAGE_FILE = {'javascript': ["html"], 28 | 'css': ["html"], 29 | 'html': ["html","css"]}; 30 | 31 | var QUOTED_ATTR = ["id","class"]; 32 | 33 | var PREFIXES = {'html_imp': {'id': '="','class': '="'}}; 34 | var SUFFIXES = {'html_imp': {'data-': '="++"'}, 35 | 'html_exp': {'data-': '="++"'}, 36 | 'css': {'id': ' {\n\t++\n}','class': ' {\n\t++\n}'}, 37 | 'javascript': {'on': "', "} 38 | }; 39 | 40 | var HTML_HINT_ATTR = ["id","class","data-"]; 41 | var JS_HINT_ATTR = ["id","class","data","hasClass","removeClass","addClass","on"]; 42 | 43 | var HTML_HTML_REGEX = {'class': REGEX_HTML_CLASS, 'id': REGEX_HTML_ID, 'data-': REGEX_DATA}; 44 | var HTML_CSS_REGEX = {'class': REGEX_CSS_CLASS, 'id': REGEX_CSS_ID}; 45 | var JCSS__REGEX = {'class': REGEX_HTML_CLASS, 'id': REGEX_HTML_ID, 'data': REGEX_DATA}; 46 | 47 | var ATTR_REDIRECT = {'addClass': "class", 'hasClass': "class",'removeClass': "class",'removeData': 'data'}; 48 | 49 | var SPECIAL_ATTR = {'javascript': {'on': ['change','click','dblclick','focus','focusin','focusout','hover','keydown', 50 | 'keypress','keyup','mousedown','mouseenter','mouseleave','mousemove','mouseover', 51 | 'mouseout','mouseup','resize','scroll','select','submit']}}; 52 | 53 | /** 54 | * reverse a string 55 | */ 56 | function reverse_str(s){ 57 | return s.split("").reverse().join(""); 58 | } 59 | 60 | /** 61 | * JQueryHinter constructor 62 | */ 63 | function JQueryHinter(importFuncs) { 64 | this.implicitChar = ''; 65 | this.HTML_AND_CSS_LANGUAGES = LANGUAGES.html.slice(); 66 | Array.prototype.push.apply(this.HTML_AND_CSS_LANGUAGES,LANGUAGES.css); 67 | 68 | this.READ_FILES = []; 69 | var file_types = ['html','css']; 70 | for (var i = 0; i < file_types.length; i++) { 71 | Array.prototype.push.apply(this.READ_FILES,LANGUAGES[file_types[i]]); 72 | } 73 | this.REGEX_HTML = new RegExp(REGEX_HTML_ID.source+"|"+REGEX_HTML_CLASS.source+"|"+REGEX_DATA.source,'ig'); 74 | this.REGEX_CSS = new RegExp(REGEX_CSS_ID.source+"|"+REGEX_CSS_CLASS.source,'ig'); 75 | this.REGEX_2_CLASS = {'HTML': ["ID","CLASS","DATA"],'CSS': ["ID","CLASS"]}; 76 | 77 | this.reverseLanguage = {}; 78 | for(var key in LANGUAGES) { 79 | for (var i = 0; i < LANGUAGES[key].length; i++) { 80 | this.reverseLanguage[LANGUAGES[key][i]] = key; 81 | } 82 | } 83 | 84 | this.match = ''; 85 | this.allHints = {}; 86 | 87 | this.prefixHint = ''; 88 | this.suffixHint = ''; 89 | this.setCursor = false; 90 | 91 | this.fillCache(); 92 | } 93 | 94 | /** 95 | * Fill all cached data (this.allHints) 96 | * @param {String|Boolean} fileType set false if not set otherwise use update only that fileExtensionKey 97 | */ 98 | JQueryHinter.prototype.fillCache = function (fileType) { 99 | fileType = typeof fileType !== "undefined" ? fileType : false; 100 | 101 | var languageKeys = Object.keys(LANGUAGES); 102 | var supportedFileTypeCats; 103 | if (fileType === false) { 104 | supportedFileTypeCats = Object.keys(LANGUAGES); 105 | } else { 106 | supportedFileTypeCats = fileType; 107 | } 108 | var clInst = this; 109 | var supportedFileTypes; 110 | 111 | getAllFilesByFileTypes(this.READ_FILES) 112 | .done(function(files) { 113 | clInst.getHintsForFiles(files) 114 | .done(function (defHints) { 115 | clInst.updateHints(defHints); 116 | }); 117 | }); 118 | 119 | } 120 | 121 | 122 | /** 123 | * return all html files in the current project 124 | * @returns {Deferred Array} all html file names (including the path) 125 | */ 126 | function getAllFilesByFileTypes(supportedFileTypes) { 127 | var htmlFiles = []; 128 | var result = new $.Deferred(); 129 | 130 | var timerName = PerfUtils.markStart('jQueryHinterHTML'); 131 | 132 | function _nonBinaryFileFilter(file) { 133 | return supportedFileTypes.indexOf(LanguageManager.getLanguageForPath(file.fullPath)._id) >= 0; 134 | } 135 | 136 | ProjectManager.getAllFiles(_nonBinaryFileFilter) 137 | .done(function (files) { 138 | PerfUtils.addMeasurement(timerName); 139 | for(var i = 0; i < files.length; i++) { 140 | 141 | } 142 | result.resolve(files); 143 | }) 144 | .fail(function () { 145 | PerfUtils.finalizeMeasurement(timerName); 146 | result.reject(); 147 | }); 148 | 149 | return result.promise(); 150 | } 151 | 152 | /** 153 | * Return all hints for the implicitChar 154 | * @param {Array} files Brackets file array 155 | * @returns {DeferredArray} an array with all hints 156 | */ 157 | JQueryHinter.prototype.getHintsForFiles = function (files) { 158 | var clInst = this; 159 | var result = new $.Deferred(); 160 | var index = 0; 161 | var hints = {}; 162 | 163 | // no files at all 164 | if (files.length == 0) { 165 | result.resolve(hints); 166 | return result.promise(); 167 | } 168 | 169 | getHintsForFilesRec(index) 170 | .done(function() { 171 | result.resolve(hints); 172 | }).fail(function(e) { 173 | result.reject(e); 174 | }); 175 | 176 | return result.promise(); 177 | 178 | function getHintsForFilesRec(index) { 179 | var resultRec = new $.Deferred(); 180 | clInst.getHintsForSingleFile(files[index]) 181 | .done(function(hintsForSingleFile) { 182 | 183 | hints = hintExtend(hints,hintsForSingleFile); 184 | 185 | if (index+1 < files.length) { 186 | getHintsForFilesRec(index+1) 187 | .done(function() { 188 | resultRec.resolve(); 189 | }); 190 | } else { 191 | resultRec.resolve(); 192 | } 193 | }) 194 | .fail(function(e) { 195 | return resultRec.reject(); 196 | }); 197 | return resultRec.promise(); 198 | } 199 | } 200 | 201 | /** 202 | * Return all hints for the implicitChar in a single file 203 | * @param {Object} file Brackets file object 204 | * @returns {DeferredArray} an array with all hints 205 | */ 206 | JQueryHinter.prototype.getHintsForSingleFile = function (file) { 207 | var result = new $.Deferred(); 208 | var hRegex; 209 | var clInst = this; 210 | var hints = {}; 211 | 212 | var fileExt = file._name.split('.').pop(); 213 | switch (fileExt) { 214 | case "htm": 215 | fileExt = "html"; 216 | break; 217 | } 218 | 219 | if (LANGUAGES.html.indexOf(fileExt) >= 0 || LANGUAGES.css.indexOf(fileExt) >= 0) { 220 | var go = true; 221 | var lang; 222 | if (LANGUAGES.html.indexOf(fileExt) >= 0) { 223 | hRegex = this.REGEX_HTML; 224 | lang = "HTML"; 225 | } else if (LANGUAGES.css.indexOf(fileExt) >= 0) { 226 | hRegex = this.REGEX_CSS; 227 | lang = "CSS"; 228 | } else { 229 | result.resolve(hints); 230 | go = false; 231 | } 232 | if (go) { 233 | DocumentManager.getDocumentText(file) 234 | .done(function(doc) { 235 | 236 | var regMath = null; 237 | while ((regMatch = hRegex.exec(doc)) !== null) { 238 | var type; 239 | var typeID; 240 | for (var i = 1; i < clInst.REGEX_2_CLASS[lang].length+1; i++) { 241 | if (typeof regMatch[i] !== "undefined") { 242 | type = clInst.REGEX_2_CLASS[lang][i-1]; 243 | typeID = i; 244 | break; 245 | } 246 | } 247 | 248 | var splittedRegMatches = regMatch[typeID].split(' '); 249 | for (var i = 0; i < splittedRegMatches.length; i++) { 250 | var splittedRegMatch = clInst.prefixHint+splittedRegMatches[i]; 251 | if (!hints[type]) { 252 | hints[type] = []; 253 | } 254 | hints[type].push(splittedRegMatch); 255 | } 256 | } 257 | 258 | result.resolve(hints); 259 | }).fail(function(e) { 260 | result.reject(e); 261 | }); 262 | } 263 | } else { 264 | result.resolve(hints); 265 | } 266 | return result.promise(); 267 | } 268 | 269 | /** 270 | * extend the hints using arr 271 | * @param {Object} hints the old hints 272 | * @param {Object} arr new hints that will extend the old ones 273 | * @returns {Object} the combined hints 274 | */ 275 | function hintExtend(hints,arr){ 276 | var arrKeys = Object.keys(arr); 277 | 278 | var arrKey; 279 | for (var i = 0; i < arrKeys.length; i++) { 280 | arrKey = arrKeys[i]; 281 | if (!hints[arrKey]) { 282 | hints[arrKey] = []; 283 | } 284 | Array.prototype.push.apply(hints[arrKey],arr[arrKey]); 285 | } 286 | return hints; 287 | } 288 | 289 | /** 290 | * Get unique elements in an array using the column index 291 | * @param {Number} [index=false] index of the column that should be unique (not set => unique in total) 292 | * @returns {Array} array where the elements in the defined index are unique 293 | */ 294 | Array.prototype.getUnique = function(index){ 295 | index = typeof(index) === "undefined" ? false : index; 296 | var u = {}, a = []; 297 | for(var i = 0, l = this.length; i < l; ++i){ 298 | if (index === false) { 299 | if(u.hasOwnProperty(this[i])) { 300 | continue; 301 | } 302 | } else { 303 | if(u.hasOwnProperty(this[i][index])) { 304 | continue; 305 | } 306 | } 307 | a.push(this[i]); 308 | if (index === false) { 309 | u[this[i]] = 1; 310 | } else { 311 | u[this[i][index]] = 1; 312 | } 313 | } 314 | return a; 315 | } 316 | 317 | /** 318 | * update the hints using a single file 319 | * @param {Object} file Brackets file object 320 | */ 321 | JQueryHinter.prototype.updateFile = function (file) { 322 | var clInst = this; 323 | var fileExt = file._name.split('.').pop(); 324 | switch (fileExt) { 325 | case "htm": 326 | fileExt = "html"; 327 | break; 328 | } 329 | 330 | if (this.READ_FILES.indexOf(fileExt) >= 0) { 331 | // console.time('updateFile'); 332 | clInst.getHintsForSingleFile(file) 333 | .done(function(hints) { 334 | hints = hintExtend(clInst.allHints,hints); 335 | clInst.updateHints(hints); 336 | // console.log('updated: ',clInst.allHints); 337 | // console.timeEnd('updateFile'); 338 | }); 339 | } 340 | } 341 | 342 | /** 343 | * Update the hints (this.allHints) using the new hints hints 344 | * @param {Array} hints new hints 345 | */ 346 | JQueryHinter.prototype.updateHints = function (hints) { 347 | var clInst = this; 348 | var hintKeys = Object.keys(hints); 349 | var hintKey; 350 | var cHints; 351 | for (var i = 0; i < hintKeys.length; i++) { 352 | hintKey = hintKeys[i]; 353 | cHints = hints[hintKey]; 354 | cHints = cHints.getUnique(); 355 | hints[hintKey] = cHints; 356 | } 357 | 358 | clInst.allHints = hints; 359 | } 360 | 361 | /** 362 | * Get the current attribute (before the cursor position) 363 | * @returns {String} name of the attribute i.e "class" or "id" 364 | */ 365 | JQueryHinter.prototype.getCurrentAttr = function () { 366 | var attr; 367 | var line = this.editor.document.getRange({line:this.pos.line,ch:0},this.pos); 368 | var lineRevInitial = reverse_str(line); 369 | var lineRev = lineRevInitial; 370 | 371 | 372 | if (LANGUAGES.html.indexOf(this.language) >= 0) { 373 | var match = lineRev.match(/^[^="]+"=/); 374 | 375 | if (match) { 376 | lineRev = lineRev.substr(match[0].length); 377 | this.noFixes = true; 378 | } 379 | 380 | attr = reverse_str(lineRev.split(" ")[0]); 381 | 382 | var last2Char = attr.substr(-2); 383 | if (last2Char != '="' && last2Char != "='" && QUOTED_ATTR.indexOf(attr) >= 0) { 384 | this.setFixes('="','"'); 385 | } else if (QUOTED_ATTR.indexOf(attr.substr(0,attr.length-2)) >= 0) { 386 | attr = attr.substr(0,attr.length-2); 387 | } 388 | } else if (LANGUAGES.javascript.indexOf(this.language) >= 0) { 389 | // substr(2) because we don't want the '( 390 | attr = reverse_str(lineRev.split(".")[0].substr(2)); 391 | } 392 | 393 | // if the implicitChar is set the first part of lineRev must be the attr (in all HTML files) 394 | if (this.implicitChar && LANGUAGES.html.indexOf(this.language) >= 0) { 395 | if (reverse_str(lineRevInitial.substr(0,attr.length)) != attr) { 396 | return false; 397 | } 398 | } 399 | 400 | if (attr in ATTR_REDIRECT) { 401 | attr = ATTR_REDIRECT[attr]; 402 | } 403 | this.checkFixes(attr); 404 | return attr; 405 | } 406 | 407 | 408 | /** 409 | * Check if it's possible to add fixes 410 | * + data-... should add an ="++" as suffix 411 | * @param {String} attr attribute like 'class' or 'data-' 412 | */ 413 | JQueryHinter.prototype.checkFixes = function (attr, line) { 414 | var prefix, suffix; 415 | 416 | var langReversed = this.reverseLanguage[this.language]; 417 | // html has two different suffix systems one for implicitChar and one for explicit 418 | if (langReversed == "html") { 419 | if (!this.implicitChar) { 420 | langReversed += "_exp"; 421 | } else { 422 | langReversed += "_imp"; 423 | } 424 | } 425 | 426 | 427 | if (!(langReversed in PREFIXES) || !(attr in PREFIXES[langReversed])) { 428 | prefix = ''; 429 | } else { 430 | prefix = PREFIXES[langReversed][attr]; 431 | } 432 | if (!(langReversed in SUFFIXES) || !(attr in SUFFIXES[langReversed])) { 433 | suffix = ''; 434 | } else { 435 | suffix = SUFFIXES[langReversed][attr]; 436 | } 437 | 438 | // check if suffix must be set 439 | if (suffix.indexOf('{') >= 0 && line.indexOf('{') >= 0) { 440 | suffix = ''; 441 | } 442 | 443 | this.setFixes(prefix,suffix); 444 | } 445 | 446 | 447 | 448 | /** 449 | * Add prefix and suffix 450 | * @param {String} prefix prefix 451 | * @param {String} suffix suffix 452 | */ 453 | JQueryHinter.prototype.setFixes = function (prefix,suffix) { 454 | suffix = typeof suffix !== 'undefined' ? suffix : ''; 455 | if (!this.noFixes) { 456 | this.prefixHint = prefix; 457 | this.suffixHint = suffix; 458 | if (this.suffixHint.indexOf('++') >= 0) { 459 | this.setCursor = true; 460 | } 461 | } 462 | } 463 | 464 | 465 | /** 466 | * Checks, if it is possible to give hints inside the current docblock. 467 | * @param {editor} editor current brackets editor 468 | * @param {String} implicitChar implicit character 469 | * @returns {Boolean} true for has hints otherwise false 470 | */ 471 | JQueryHinter.prototype.hasHints = function (editor, implicitChar) { 472 | this.language = editor.getLanguageForSelection()._id; 473 | this.editor = editor; 474 | this.selection = editor.getSelectedText(); 475 | this.pos = editor.getCursorPos(); 476 | this.prefixHint = ''; 477 | this.suffixHint = ''; 478 | this.setCursor = false; 479 | this.noFixes = false; 480 | 481 | this.implicitChar = implicitChar; 482 | 483 | 484 | // HTML Language 485 | if (LANGUAGES.html.indexOf(this.language) >= 0) { 486 | this.fileTypes = this.HTML_AND_CSS_LANGUAGES; 487 | var attributes = HTML_HINT_ATTR; 488 | var lastChars = attributes.map(function(ele) { return ele.substr(-1) }); 489 | 490 | if (!this.implicitChar || lastChars.indexOf(this.implicitChar) >= 0) { 491 | var attr = this.getCurrentAttr(); 492 | if (!attr) { return false; } 493 | 494 | if (attributes.indexOf(attr) >= 0) { 495 | this.attr = attr; 496 | return true; 497 | } 498 | } 499 | } else { // CSS and JS 500 | this.fileTypes = LANGUAGES.html; 501 | 502 | if (this.implicitChar == "'" || this.implicitChar == '"') { 503 | var attributes = JS_HINT_ATTR; 504 | var attr = this.getCurrentAttr(); 505 | if (!attr) { return false; } 506 | 507 | if (attributes.indexOf(attr) >= 0) { 508 | this.attr = attr; 509 | return true; 510 | } 511 | } else if (this.implicitChar == '.' || this.implicitChar == '#') { 512 | var line = this.editor.document.getRange({line:this.pos.line,ch:0},this.pos); 513 | if (this.implicitChar == '.') { 514 | if (LANGUAGES.css.indexOf(this.language) >= 0) { 515 | var line = this.editor.document.getLine(this.pos.line); 516 | if (!line.match(REGEX_HASHINT_CSS_CLASS)) { 517 | return false; 518 | } 519 | } else { // js 520 | var charBefore = this.editor.document.getRange({line:this.pos.line,ch:this.pos.ch-2},this.pos).charAt(0); 521 | if (charBefore !== '"' && charBefore !== "'") { 522 | return false; 523 | } 524 | } 525 | this.attr = 'class'; 526 | this.checkFixes(this.attr, line); 527 | } else if (this.implicitChar == '#') { 528 | if (LANGUAGES.css.indexOf(this.language) >= 0) { 529 | var line = this.editor.document.getLine(this.pos.line); 530 | if (!line.match(REGEX_HASHINT_CSS_ID)) { 531 | return false; 532 | } 533 | } else { // js 534 | var charBefore = this.editor.document.getRange({line:this.pos.line,ch:this.pos.ch-2},this.pos).charAt(0); 535 | if (charBefore !== '"' && charBefore !== "'") { 536 | return false; 537 | } 538 | } 539 | this.attr = 'id'; 540 | this.checkFixes(this.attr, line); 541 | } 542 | return true; 543 | } else { // maybe after a typo directly inside i.e a class name 544 | var line = this.editor.document.getRange({line:this.pos.line,ch:0},this.pos); 545 | var lineRev = reverse_str(line); 546 | var match = lineRev.match(REGEX_REVERSE_INSIDE); 547 | if (match) { 548 | this.pos.ch -= match[0].length-2; // one char for '.' or '#' and one for ' or " 549 | if (match[2] == '.') { 550 | this.attr = 'class'; 551 | } else if(match[2] == '#') { 552 | this.attr = 'id'; 553 | } else if (!(match[2])) { 554 | // maybe something else like data 555 | this.pos.ch -= 1; // there is neither a '#' nor a '.' 556 | var attributes = JS_HINT_ATTR; 557 | var attr = this.getCurrentAttr(); 558 | if (!attr) { return false; } 559 | if (attributes.indexOf(attr) >= 0) { 560 | this.attr = attr; 561 | } else { 562 | return false; 563 | } 564 | } 565 | this.checkFixes(this.attr); 566 | 567 | return true; 568 | } 569 | 570 | } 571 | 572 | } 573 | return false; 574 | }; 575 | 576 | 577 | 578 | /** 579 | * getHints for this.implicitchar 580 | * @returns {Object} HinterObject 581 | */ 582 | JQueryHinter.prototype.getHints = function () { 583 | 584 | 585 | if (this.pos.ch > this.editor.getCursorPos().ch) { 586 | return false; 587 | } 588 | var clInst = this; 589 | var result = $.Deferred(); 590 | var defHints; 591 | this.match = this.editor.document.getRange(this.pos, this.editor.getCursorPos()); 592 | 593 | // check if attr is inside special attributes like 'on' 594 | var revLang = this.reverseLanguage[this.language]; 595 | if (revLang in SPECIAL_ATTR && this.attr in SPECIAL_ATTR[revLang]) { 596 | // attr is a special attr 597 | defHints = SPECIAL_ATTR[revLang][this.attr]; 598 | } else { // normal 599 | var allHintsKey = this.attr.toUpperCase(); 600 | if (allHintsKey == "DATA-") { 601 | allHintsKey = "DATA"; 602 | } 603 | 604 | defHints = clInst.allHints[allHintsKey]; 605 | } 606 | 607 | 608 | /* 609 | console.log('this.match: ',this.match); 610 | console.log('allHints: ',this.allHints); 611 | console.log('fileTypesName: ',this.fileTypes[0]); 612 | console.log('language: ',this.language); 613 | console.log('attr: ',this.attr); 614 | console.log('prefixHint: ',this.prefixHint); 615 | console.log('suffixHint: ',this.suffixHint); 616 | */ 617 | 618 | defHints = this.getMatchingHints(defHints); 619 | if (!defHints || defHints.length == 0) { 620 | return false; 621 | } 622 | 623 | var result = { hints: defHints, 624 | match: this.match, 625 | selectInitial: true, 626 | handleWideResults: false 627 | }; 628 | return result; 629 | }; 630 | 631 | 632 | /** 633 | * Sort out hints that don't match this.match 634 | * @param {Array} hints all hints 635 | */ 636 | JQueryHinter.prototype.getMatchingHints = function (hints) { 637 | if (this.match == '') { 638 | return hints; 639 | } 640 | var cHint; 641 | var returnHints = []; 642 | var matchPos; 643 | var matchWOPrefix = this.removePrefix(); 644 | for (var i = 0; i < hints.length; i++) { 645 | cHint = hints[i]; 646 | 647 | if ((matchPos = cHint.toLowerCase().indexOf(matchWOPrefix.toLowerCase())) >= 0) { 648 | var sameCases = this.nrOfSameCases(matchPos,cHint,matchWOPrefix); 649 | returnHints.push([cHint,matchPos,sameCases]); 650 | // returnHints.push([cHint,-sameCases,-matchPos]); 651 | } 652 | } 653 | returnHints.sort(this.orderHints()); 654 | return returnHints.getCol(0); 655 | } 656 | 657 | JQueryHinter.prototype.nrOfSameCases = function(pos,hint,match) { 658 | var hint = hint.substr(pos); 659 | var c = 0; 660 | for (var i = 0; i < match.length; i++) { 661 | if (match.charAt(i) === hint.charAt(i)) { 662 | c++; 663 | } 664 | } 665 | return c; 666 | } 667 | 668 | /** 669 | * remove the prefix in this.match 670 | * @returns {String} this.match without this.prefixHint 671 | */ 672 | JQueryHinter.prototype.removePrefix = function () { 673 | return this.match.substr(this.prefixHint.length); 674 | } 675 | 676 | /** 677 | * Sorting function 678 | * sort by match (second column) and then by number of correct cases (lower,upper) (third column) 679 | * @param {Array} a array of the first comparable 680 | * @param {Array} b array of the second comparable 681 | * @returns {Number} 0 if equal 1 if first is greater, else: -1 682 | */ 683 | JQueryHinter.prototype.orderHints = function() { 684 | return function(a,b) { 685 | if(a[1]== b[1]) { 686 | if (a[2] > b[2]) { 687 | return -1; 688 | } else { 689 | return 1; 690 | } 691 | } 692 | return a[1] > b[1] ? 1: -1; 693 | } 694 | } 695 | 696 | /** 697 | * Return all elements in a column 698 | * @param {Number} col number of the column 699 | * @returns {Array} all elements in that column as array 700 | */ 701 | Array.prototype.getCol = function (col){ 702 | var column = []; 703 | for(var i=0; i< this.length; i++){ 704 | column.push(this[i][col]); 705 | } 706 | return column; 707 | } 708 | 709 | /** 710 | * Inserts the hint 711 | */ 712 | JQueryHinter.prototype.insertHint = function (hint) { 713 | // Document objects represent file contents 714 | var currentDoc = this.editor.document; 715 | 716 | // Where the range end that should be replaced 717 | var start = { 718 | line: this.pos.line, 719 | ch: this.pos.ch 720 | }; 721 | var end = { 722 | line: this.pos.line, 723 | ch: this.pos.ch + ((this.removeSelection) ? this.selection.length : this.match.length) 724 | }; 725 | 726 | 727 | // Add some text in our document 728 | currentDoc.replaceRange(this.prefixHint+hint+this.suffixHint, start, end); 729 | if (this.setCursor) { 730 | var line; 731 | var match; 732 | var i = 0; 733 | var startSetCursor, endSetCursor; 734 | while (i < this.suffixHint.length) { 735 | line = this.editor.document.getLine(start.line+i); 736 | 737 | match = line.indexOf('++'); 738 | if (match >= 0) { 739 | startSetCursor = {line: start.line+i, ch: match}; 740 | endSetCursor = {line: start.line+i, ch: match+2}; 741 | break; 742 | } 743 | i++; 744 | } 745 | currentDoc.replaceRange('', startSetCursor, endSetCursor); 746 | // Set focus on editor. 747 | MainViewManager.focusActivePane() 748 | EditorManager.getCurrentFullEditor().setCursorPos( 749 | startSetCursor.line, 750 | startSetCursor.ch); 751 | } 752 | 753 | }; 754 | 755 | return JQueryHinter; 756 | }); 757 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | */ 23 | 24 | 25 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 26 | /*global define, $, brackets, window */ 27 | 28 | /** extension to generate JSDoc annotations for functions */ 29 | define(function (require, exports, module) { 30 | 'use strict'; 31 | 32 | var AppInit = brackets.getModule("utils/AppInit"); 33 | var CodeHintManager = brackets.getModule("editor/CodeHintManager"); 34 | var DocumentManager = brackets.getModule("document/DocumentManager"); 35 | 36 | var JQueryHinter; 37 | var jqueryHinter; 38 | 39 | function fileSavedHandler($event, listener) { 40 | jqueryHinter.updateFile(listener.file); 41 | } 42 | 43 | AppInit.appReady(function () { 44 | JQueryHinter = require('hints'); 45 | 46 | jqueryHinter = new JQueryHinter(); 47 | DocumentManager.on('documentSaved', fileSavedHandler); 48 | 49 | CodeHintManager.registerHintProvider(jqueryHinter, ["javascript", "coffeescript", "livescript", "css", "less", "sass", "scss", "html", "mustache"], 0); 50 | }); 51 | 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquinter", 3 | "title": "jQuinter", 4 | "description": "Get hints for jQuery selectors in JS,HTML and CSS", 5 | "homepage": "https://github.com/Wikunia/brackets-jQuinter", 6 | "version": "0.3.3", 7 | "author": "Ole Kröger ", 8 | "license": "MIT License (MIT)", 9 | "engines": { 10 | "brackets": ">=0.20.0" 11 | } 12 | } --------------------------------------------------------------------------------