diff --git a/ide/css.editor/src/org/netbeans/modules/css/editor/module/CssModuleSupport.java b/ide/css.editor/src/org/netbeans/modules/css/editor/module/CssModuleSupport.java index f4b4ee5de706..274db601f739 100644 --- a/ide/css.editor/src/org/netbeans/modules/css/editor/module/CssModuleSupport.java +++ b/ide/css.editor/src/org/netbeans/modules/css/editor/module/CssModuleSupport.java @@ -31,6 +31,7 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.text.Document; import org.netbeans.modules.csl.api.ColoringAttributes; @@ -94,11 +95,13 @@ public static SemanticAnalyzerResult analyzeDeclaration(Node declarationNode) { } public static Map> getSemanticHighlights(FeatureContext context, FeatureCancel cancel) { - Map> all = new HashMap<>(); + long start = System.nanoTime(); + + Map> allPrecursor = new HashMap<>(); final Collection>>> visitors = new ArrayList<>(); for (CssEditorModule module : getModules()) { - NodeVisitor>> visitor = module.getSemanticHighlightingNodeVisitor(context, all); + NodeVisitor>> visitor = module.getSemanticHighlightingNodeVisitor(context, allPrecursor); //modules may return null visitor instead of a dummy empty visitor //to speed up the parse tree visiting when there're no result if (visitor != null) { @@ -122,6 +125,59 @@ public void run() { NodeVisitor.visitChildren(context.getParseTreeRoot(), visitors); + long preparation = System.nanoTime(); + + Map> all = new HashMap<>(); + + List sortedRanges = new ArrayList<>(allPrecursor.keySet()); + sortedRanges.sort(null); + + + List stack = new ArrayList<>(); + OffsetRange lastAdded; + + for(int i = 0; i < sortedRanges.size(); i++) { + OffsetRange currentItem = sortedRanges.get(i); + Set attributes = allPrecursor.get(currentItem); + OffsetRange nextItem = (i < (sortedRanges.size() - 1)) ? sortedRanges.get(i + 1) : null; + if(nextItem != null && currentItem.getEnd() > nextItem.getStart()) { + stack.add(currentItem); + currentItem = currentItem.boundTo(0, nextItem.getStart()); + } + if(! currentItem.isEmpty()) { + all.put(currentItem, attributes); + } + lastAdded = currentItem; + while(true) { + if(stack.isEmpty()) { + break; + } + OffsetRange stackElement = stack.remove(stack.size() - 1); + boolean endStackProcessing = false; + if(nextItem != null && stackElement.getEnd() > nextItem.getStart()) { + stack.add(stackElement); + endStackProcessing = true; + } + Set stackAttributes = allPrecursor.get(stackElement); + stackElement = stackElement.boundTo(lastAdded.getEnd(), nextItem != null ? nextItem.getStart() : Integer.MAX_VALUE); + if(! stackElement.isEmpty()) { + all.put(stackElement, stackAttributes); + lastAdded = stackElement; + } + if(endStackProcessing) { + break; + } + } + } + + long consolidation = System.nanoTime(); + + if(LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "Preparation: {0} ms, Consolidation: {1} ms", new Object[]{preparation - start, consolidation - preparation}); + LOGGER.log(Level.FINER, "Precursor: {0}", allPrecursor); + LOGGER.log(Level.FINER, "Final: {0}", all); + } + return all; } diff --git a/ide/css.editor/test/unit/data/testfiles/coloring1.css b/ide/css.editor/test/unit/data/testfiles/coloring1.css new file mode 100644 index 000000000000..b78b69666fc1 --- /dev/null +++ b/ide/css.editor/test/unit/data/testfiles/coloring1.css @@ -0,0 +1,6 @@ +[type="checkbox"]:nth-child(2n), +[type="checkbox"]:not([hidden]), +[type="checkbox"]:not(.active), +[type="checkbox"]:not(:checked), +[type="checkbox"]:checked { +} \ No newline at end of file diff --git a/ide/css.editor/test/unit/data/testfiles/coloring1.css.semantic b/ide/css.editor/test/unit/data/testfiles/coloring1.css.semantic new file mode 100644 index 000000000000..27d0398f5a7a --- /dev/null +++ b/ide/css.editor/test/unit/data/testfiles/coloring1.css.semantic @@ -0,0 +1,6 @@ +[|>CUSTOM1:type<|="checkbox"]|>CLASS::nth-child(2n)<|, +[|>CUSTOM1:type<|="checkbox"]|>CLASS::not([|>CUSTOM1:<|hidden|>CLASS:<|])<|, +[|>CUSTOM1:type<|="checkbox"]|>CLASS::not(|>METHOD:<|.active|>CLASS:<|)<|, +[|>CUSTOM1:type<|="checkbox"]|>CLASS::not(|>CLASS:<|:checked|>CLASS:<|)<|, +[|>CUSTOM1:type<|="checkbox"]|>CLASS::checked<| { +} \ No newline at end of file diff --git a/ide/css.editor/test/unit/src/org/netbeans/modules/css/editor/csl/CssSemanticAnalyzerTest.java b/ide/css.editor/test/unit/src/org/netbeans/modules/css/editor/csl/CssSemanticAnalyzerTest.java new file mode 100644 index 000000000000..045b0a6ba31c --- /dev/null +++ b/ide/css.editor/test/unit/src/org/netbeans/modules/css/editor/csl/CssSemanticAnalyzerTest.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.css.editor.csl; + +import org.netbeans.modules.css.editor.module.main.CssModuleTestBase; +import org.netbeans.modules.css.lib.api.CssParserResult; + +public class CssSemanticAnalyzerTest extends CssModuleTestBase { + + public CssSemanticAnalyzerTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + CssParserResult.IN_UNIT_TESTS = true; + } + + public void testColoring1() throws Exception { + checkSemantic("testfiles/coloring1.css"); + } +}