1/* 2 * Copyright (C) 2010 Nikita Vasilyev. All rights reserved. 3 * Copyright (C) 2010 Joseph Pecoraro. All rights reserved. 4 * Copyright (C) 2010 Google Inc. All rights reserved. 5 * Copyright (C) 2013 Apple Inc. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions are 9 * met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * * Redistributions in binary form must reproduce the above 14 * copyright notice, this list of conditions and the following disclaimer 15 * in the documentation and/or other materials provided with the 16 * distribution. 17 * * Neither the name of Google Inc. nor the names of its 18 * contributors may be used to endorse or promote products derived from 19 * this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34WebInspector.CSSCompletions = function(properties, acceptEmptyPrefix) 35{ 36 this._values = []; 37 this._longhands = {}; 38 this._shorthands = {}; 39 40 for (var i = 0; i < properties.length; ++i) { 41 // COMPATIBILITY (iOS 6): This used to be an array of strings, 42 // now it contains objects with a 'name' property. Support both here. 43 var property = properties[i]; 44 if (typeof property === "string") { 45 this._values.push(property); 46 continue; 47 } 48 49 var propertyName = property.name; 50 this._values.push(propertyName); 51 52 var longhands = property.longhands; 53 if (longhands) { 54 this._longhands[propertyName] = longhands; 55 56 for (var j = 0; j < longhands.length; ++j) { 57 var longhandName = longhands[j]; 58 59 var shorthands = this._shorthands[longhandName]; 60 if (!shorthands) { 61 shorthands = []; 62 this._shorthands[longhandName] = shorthands; 63 } 64 65 shorthands.push(propertyName); 66 } 67 } 68 } 69 70 this._values.sort(); 71 72 this._acceptEmptyPrefix = acceptEmptyPrefix; 73} 74 75WebInspector.CSSCompletions.cssNameCompletions = null; 76 77WebInspector.CSSCompletions.requestCSSNameCompletions = function() 78{ 79 function propertyNamesCallback(error, names) 80 { 81 if (error) 82 return; 83 84 WebInspector.CSSCompletions.cssNameCompletions = new WebInspector.CSSCompletions(names, false); 85 86 var propertyNamesForCodeMirror = {}; 87 var valueKeywordsForCodeMirror = {"inherit": true, "initial": true}; 88 var colorKeywordsForCodeMirror = {}; 89 90 function nameForCodeMirror(name) 91 { 92 // CodeMirror parses the vendor prefix separate from the property or keyword name, 93 // so we need to strip vendor prefixes from our names. Also strip function parenthesis. 94 return name.replace(/^-[^-]+-/, "").replace(/\(\)$/, ""); 95 } 96 97 function collectPropertyNameForCodeMirror(propertyName) 98 { 99 // Properties can also be value keywords, like when used in a transition. 100 // So we add them to both lists. 101 var codeMirrorPropertyName = nameForCodeMirror(propertyName); 102 propertyNamesForCodeMirror[codeMirrorPropertyName] = true; 103 valueKeywordsForCodeMirror[codeMirrorPropertyName] = true; 104 } 105 106 for (var i = 0; i < names.length; ++i) { 107 // COMPATIBILITY (iOS 6): This used to be an array of strings, 108 // now it contains objects with a 'name' property. Support both here. 109 var property = names[i]; 110 if (typeof property === "string") 111 collectPropertyNameForCodeMirror(property); 112 else 113 collectPropertyNameForCodeMirror(property.name); 114 } 115 116 for (var propertyName in WebInspector.CSSKeywordCompletions._propertyKeywordMap) { 117 var keywords = WebInspector.CSSKeywordCompletions._propertyKeywordMap[propertyName]; 118 for (var i = 0; i < keywords.length; ++i) { 119 // Skip numbers, like the ones defined for font-weight. 120 if (!isNaN(Number(keywords[i]))) 121 continue; 122 valueKeywordsForCodeMirror[nameForCodeMirror(keywords[i])] = true; 123 } 124 } 125 126 WebInspector.CSSKeywordCompletions._colors.forEach(function(colorName) { 127 colorKeywordsForCodeMirror[nameForCodeMirror(colorName)] = true; 128 }); 129 130 function updateCodeMirrorCSSMode(mimeType) 131 { 132 var modeSpec = CodeMirror.resolveMode(mimeType); 133 134 console.assert(modeSpec.propertyKeywords); 135 console.assert(modeSpec.valueKeywords); 136 console.assert(modeSpec.colorKeywords); 137 138 modeSpec.propertyKeywords = propertyNamesForCodeMirror; 139 modeSpec.valueKeywords = valueKeywordsForCodeMirror; 140 modeSpec.colorKeywords = colorKeywordsForCodeMirror; 141 142 CodeMirror.defineMIME(mimeType, modeSpec); 143 } 144 145 updateCodeMirrorCSSMode("text/css"); 146 updateCodeMirrorCSSMode("text/x-scss"); 147 } 148 149 if (window.CSSAgent) 150 CSSAgent.getSupportedCSSProperties(propertyNamesCallback); 151} 152 153WebInspector.CSSCompletions.prototype = { 154 get values() 155 { 156 return this._values; 157 }, 158 159 startsWith: function(prefix) 160 { 161 var firstIndex = this._firstIndexOfPrefix(prefix); 162 if (firstIndex === -1) 163 return []; 164 165 var results = []; 166 while (firstIndex < this._values.length && this._values[firstIndex].startsWith(prefix)) 167 results.push(this._values[firstIndex++]); 168 return results; 169 }, 170 171 firstStartsWith: function(prefix) 172 { 173 var foundIndex = this._firstIndexOfPrefix(prefix); 174 return (foundIndex === -1 ? "" : this._values[foundIndex]); 175 }, 176 177 _firstIndexOfPrefix: function(prefix) 178 { 179 if (!this._values.length) 180 return -1; 181 if (!prefix) 182 return this._acceptEmptyPrefix ? 0 : -1; 183 184 var maxIndex = this._values.length - 1; 185 var minIndex = 0; 186 var foundIndex; 187 188 do { 189 var middleIndex = (maxIndex + minIndex) >> 1; 190 if (this._values[middleIndex].startsWith(prefix)) { 191 foundIndex = middleIndex; 192 break; 193 } 194 if (this._values[middleIndex] < prefix) 195 minIndex = middleIndex + 1; 196 else 197 maxIndex = middleIndex - 1; 198 } while (minIndex <= maxIndex); 199 200 if (foundIndex === undefined) 201 return -1; 202 203 while (foundIndex && this._values[foundIndex - 1].startsWith(prefix)) 204 foundIndex--; 205 206 return foundIndex; 207 }, 208 209 keySet: function() 210 { 211 if (!this._keySet) 212 this._keySet = this._values.keySet(); 213 return this._keySet; 214 }, 215 216 next: function(str, prefix) 217 { 218 return this._closest(str, prefix, 1); 219 }, 220 221 previous: function(str, prefix) 222 { 223 return this._closest(str, prefix, -1); 224 }, 225 226 _closest: function(str, prefix, shift) 227 { 228 if (!str) 229 return ""; 230 231 var index = this._values.indexOf(str); 232 if (index === -1) 233 return ""; 234 235 if (!prefix) { 236 index = (index + this._values.length + shift) % this._values.length; 237 return this._values[index]; 238 } 239 240 var propertiesWithPrefix = this.startsWith(prefix); 241 var j = propertiesWithPrefix.indexOf(str); 242 j = (j + propertiesWithPrefix.length + shift) % propertiesWithPrefix.length; 243 return propertiesWithPrefix[j]; 244 }, 245 246 isShorthandPropertyName: function(shorthand) 247 { 248 return shorthand in this._longhands; 249 }, 250 251 isLonghandPropertyName: function(longhand) 252 { 253 return longhand in this._shorthands; 254 }, 255 256 longhandsForShorthand: function(shorthand) 257 { 258 return this._longhands[shorthand] || []; 259 }, 260 261 shorthandsForLonghand: function(longhand) 262 { 263 return this._shorthands[longhand] || []; 264 } 265} 266