CSSCompletions.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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. /**
  34. * @constructor
  35. */
  36. WebInspector.CSSCompletions = function(properties, acceptEmptyPrefix)
  37. {
  38. this._values = [];
  39. this._longhands = {};
  40. this._shorthands = {};
  41. for (var i = 0; i < properties.length; ++i) {
  42. // COMPATIBILITY (iOS 6): This used to be an array of strings,
  43. // now it contains objects with a 'name' property. Support both here.
  44. var property = properties[i];
  45. if (typeof property === "string") {
  46. this._values.push(property);
  47. continue;
  48. }
  49. var propertyName = property.name;
  50. this._values.push(propertyName);
  51. var longhands = property.longhands;
  52. if (longhands) {
  53. this._longhands[propertyName] = longhands;
  54. for (var j = 0; j < longhands.length; ++j) {
  55. var longhandName = longhands[j];
  56. var shorthands = this._shorthands[longhandName];
  57. if (!shorthands) {
  58. shorthands = [];
  59. this._shorthands[longhandName] = shorthands;
  60. }
  61. shorthands.push(propertyName);
  62. }
  63. }
  64. }
  65. this._values.sort();
  66. this._acceptEmptyPrefix = acceptEmptyPrefix;
  67. }
  68. /**
  69. * @type {WebInspector.CSSCompletions}
  70. */
  71. WebInspector.CSSCompletions.cssNameCompletions = null;
  72. WebInspector.CSSCompletions.requestCSSNameCompletions = function()
  73. {
  74. function propertyNamesCallback(error, names)
  75. {
  76. if (error)
  77. return;
  78. WebInspector.CSSCompletions.cssNameCompletions = new WebInspector.CSSCompletions(names, false);
  79. var propertyNamesForCodeMirror = {};
  80. var valueKeywordsForCodeMirror = {"inherit": true, "initial": true};
  81. var colorKeywordsForCodeMirror = {};
  82. function nameForCodeMirror(name)
  83. {
  84. // CodeMirror parses the vendor prefix separate from the property or keyword name,
  85. // so we need to strip vendor prefixes from our names. Also strip function parenthesis.
  86. return name.replace(/^-[^-]+-/, "").replace(/\(\)$/, "");
  87. }
  88. function collectPropertyNameForCodeMirror(propertyName)
  89. {
  90. // Properties can also be value keywords, like when used in a -webkit-transition.
  91. // So we add them to both lists.
  92. var codeMirrorPropertyName = nameForCodeMirror(propertyName);
  93. propertyNamesForCodeMirror[codeMirrorPropertyName] = true;
  94. valueKeywordsForCodeMirror[codeMirrorPropertyName] = true;
  95. }
  96. for (var i = 0; i < names.length; ++i) {
  97. // COMPATIBILITY (iOS 6): This used to be an array of strings,
  98. // now it contains objects with a 'name' property. Support both here.
  99. var property = names[i];
  100. if (typeof property === "string")
  101. collectPropertyNameForCodeMirror(property);
  102. else
  103. collectPropertyNameForCodeMirror(property.name);
  104. }
  105. for (var propertyName in WebInspector.CSSKeywordCompletions._propertyKeywordMap) {
  106. var keywords = WebInspector.CSSKeywordCompletions._propertyKeywordMap[propertyName];
  107. for (var i = 0; i < keywords.length; ++i) {
  108. // Skip numbers, like the ones defined for font-weight.
  109. if (!isNaN(Number(keywords[i])))
  110. continue;
  111. valueKeywordsForCodeMirror[nameForCodeMirror(keywords[i])] = true;
  112. }
  113. }
  114. WebInspector.CSSKeywordCompletions._colors.forEach(function(colorName) {
  115. colorKeywordsForCodeMirror[nameForCodeMirror(colorName)] = true;
  116. });
  117. function updateCodeMirrorCSSMode(mimeType)
  118. {
  119. var modeSpec = CodeMirror.resolveMode(mimeType);
  120. console.assert(modeSpec.propertyKeywords);
  121. console.assert(modeSpec.valueKeywords);
  122. console.assert(modeSpec.colorKeywords);
  123. modeSpec.propertyKeywords = propertyNamesForCodeMirror;
  124. modeSpec.valueKeywords = valueKeywordsForCodeMirror;
  125. modeSpec.colorKeywords = colorKeywordsForCodeMirror;
  126. CodeMirror.defineMIME(mimeType, modeSpec);
  127. }
  128. updateCodeMirrorCSSMode("text/css");
  129. updateCodeMirrorCSSMode("text/x-scss");
  130. }
  131. CSSAgent.getSupportedCSSProperties(propertyNamesCallback);
  132. }
  133. WebInspector.CSSCompletions.prototype = {
  134. get values()
  135. {
  136. return this._values;
  137. },
  138. startsWith: function(prefix)
  139. {
  140. var firstIndex = this._firstIndexOfPrefix(prefix);
  141. if (firstIndex === -1)
  142. return [];
  143. var results = [];
  144. while (firstIndex < this._values.length && this._values[firstIndex].startsWith(prefix))
  145. results.push(this._values[firstIndex++]);
  146. return results;
  147. },
  148. firstStartsWith: function(prefix)
  149. {
  150. var foundIndex = this._firstIndexOfPrefix(prefix);
  151. return (foundIndex === -1 ? "" : this._values[foundIndex]);
  152. },
  153. _firstIndexOfPrefix: function(prefix)
  154. {
  155. if (!this._values.length)
  156. return -1;
  157. if (!prefix)
  158. return this._acceptEmptyPrefix ? 0 : -1;
  159. var maxIndex = this._values.length - 1;
  160. var minIndex = 0;
  161. var foundIndex;
  162. do {
  163. var middleIndex = (maxIndex + minIndex) >> 1;
  164. if (this._values[middleIndex].startsWith(prefix)) {
  165. foundIndex = middleIndex;
  166. break;
  167. }
  168. if (this._values[middleIndex] < prefix)
  169. minIndex = middleIndex + 1;
  170. else
  171. maxIndex = middleIndex - 1;
  172. } while (minIndex <= maxIndex);
  173. if (foundIndex === undefined)
  174. return -1;
  175. while (foundIndex && this._values[foundIndex - 1].startsWith(prefix))
  176. foundIndex--;
  177. return foundIndex;
  178. },
  179. keySet: function()
  180. {
  181. if (!this._keySet)
  182. this._keySet = this._values.keySet();
  183. return this._keySet;
  184. },
  185. next: function(str, prefix)
  186. {
  187. return this._closest(str, prefix, 1);
  188. },
  189. previous: function(str, prefix)
  190. {
  191. return this._closest(str, prefix, -1);
  192. },
  193. _closest: function(str, prefix, shift)
  194. {
  195. if (!str)
  196. return "";
  197. var index = this._values.indexOf(str);
  198. if (index === -1)
  199. return "";
  200. if (!prefix) {
  201. index = (index + this._values.length + shift) % this._values.length;
  202. return this._values[index];
  203. }
  204. var propertiesWithPrefix = this.startsWith(prefix);
  205. var j = propertiesWithPrefix.indexOf(str);
  206. j = (j + propertiesWithPrefix.length + shift) % propertiesWithPrefix.length;
  207. return propertiesWithPrefix[j];
  208. },
  209. isShorthandPropertyName: function(shorthand)
  210. {
  211. return shorthand in this._longhands;
  212. },
  213. isLonghandPropertyName: function(longhand)
  214. {
  215. return longhand in this._shorthands;
  216. },
  217. longhandsForShorthand: function(shorthand)
  218. {
  219. return this._longhands[shorthand] || [];
  220. },
  221. shorthandsForLonghand: function(longhand)
  222. {
  223. return this._shorthands[longhand] || [];
  224. }
  225. }