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