| /* |
| * 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.javascript2.jquery.editor; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.text.Document; |
| import org.netbeans.api.lexer.Token; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.modules.csl.api.ElementHandle; |
| import org.netbeans.modules.csl.api.ElementKind; |
| import org.netbeans.modules.csl.api.HtmlFormatter; |
| import org.netbeans.modules.csl.api.Modifier; |
| import org.netbeans.modules.csl.api.OffsetRange; |
| import org.netbeans.modules.csl.spi.ParserResult; |
| import org.netbeans.modules.css.refactoring.api.CssRefactoring; |
| import org.netbeans.modules.css.refactoring.api.EntryHandle; |
| import org.netbeans.modules.css.refactoring.api.RefactoringElementType; |
| import org.netbeans.modules.javascript2.lexer.api.JsTokenId; |
| import org.netbeans.modules.javascript2.lexer.api.LexUtilities; |
| import org.netbeans.modules.javascript2.editor.spi.DeclarationFinder; |
| import org.netbeans.modules.javascript2.jquery.model.JQueryUtils; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| |
| /** |
| * |
| * @author Petr Pisl |
| */ |
| @DeclarationFinder.Registration(priority=10) |
| public class JQueryDeclarationFinder implements DeclarationFinder { |
| |
| @Override |
| public DeclarationLocation findDeclaration(ParserResult info, int caretOffset) { |
| if (JQueryUtils.isJQuery(info, caretOffset)) { |
| TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(info.getSnapshot().getTokenHierarchy(), caretOffset); |
| Rule rule = getRule(ts, caretOffset); |
| if (rule != null) { |
| RefactoringElementType type = rule.rule.charAt(0) == '#' ? RefactoringElementType.ID : RefactoringElementType.CLASS; |
| Map<FileObject, Collection<EntryHandle>> findAll = CssRefactoring.findAllOccurances(rule.rule.substring(1), type, info.getSnapshot().getSource().getFileObject(), true); |
| if (findAll == null) { |
| return DeclarationLocation.NONE; |
| } |
| DeclarationLocation dl = null; |
| for (Map.Entry<FileObject, Collection<EntryHandle>> entry : findAll.entrySet()) { |
| FileObject f = entry.getKey(); |
| Collection<EntryHandle> entries = entry.getValue(); |
| for (EntryHandle entryHandle : entries) { |
| //grrr, the main declarationlocation must be also added to the alternatives |
| //if there are more than one |
| DeclarationLocation dloc = new DeclarationLocation(f, entryHandle.entry().getDocumentRange().getStart()); |
| if (dl == null) { |
| //ugly DeclarationLocation alternatives handling workaround - one of the |
| //locations simply must be "main"!!! |
| dl = dloc; |
| } |
| AlternativeLocation aloc = new AlternativeLocationImpl(dloc, entryHandle, type); |
| dl.addAlternative(aloc); |
| } |
| } |
| |
| //and finally if there was just one entry, remove the "alternative" |
| if (dl != null && dl.getAlternativeLocations().size() == 1) { |
| dl.getAlternativeLocations().clear(); |
| } |
| |
| if (dl != null) { |
| return dl; |
| } |
| } |
| |
| } |
| return DeclarationLocation.NONE; |
| } |
| |
| @Override |
| public OffsetRange getReferenceSpan(final Document doc, final int caretOffset) { |
| final OffsetRange[] value = new OffsetRange[1]; |
| doc.render(new Runnable() { |
| |
| @Override |
| public void run() { |
| TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(doc, caretOffset); |
| Rule rule = getRule(ts, caretOffset); |
| if (rule != null) { |
| value[0] = new OffsetRange(rule.startOffset, rule.endOffset); |
| } else { |
| value[0] = OffsetRange.NONE; |
| } |
| } |
| }); |
| |
| return value[0]; |
| } |
| |
| private static class Rule { |
| String rule; |
| int startOffset; |
| int endOffset; |
| |
| public Rule(String rule, int startOffset, int endOffset) { |
| this.rule = rule; |
| this.startOffset = startOffset; |
| this.endOffset = endOffset; |
| } |
| |
| |
| } |
| |
| private Rule getRule(TokenSequence<? extends JsTokenId> ts, int caretOffset) { |
| if (ts == null) { |
| return null; |
| } |
| ts.move(caretOffset); |
| if (!(ts.movePrevious() && ts.moveNext())) { |
| return null; |
| } |
| Token<? extends JsTokenId> token = ts.token(); |
| if (token.id() == JsTokenId.STRING) { |
| String text = token.text().toString(); |
| if (text.indexOf(' ') == -1 && text.indexOf('/') > -1) { |
| // probably the string is not a rule, but path to a file |
| return null; |
| } |
| boolean isRule = false; |
| int offset = caretOffset - ts.offset(); |
| int startRule = -1; |
| while(offset > -1 && !isRule) { |
| char ch = text.charAt(offset); |
| if(ch == '.' || ch == '#') { |
| isRule = true; |
| startRule = offset; |
| } else if (ch == ' ' || ch == ':' || ch == '[') { |
| offset = 0; |
| } |
| offset --; |
| } |
| if (isRule) { |
| |
| int endRule = -1; |
| offset = startRule + 1; |
| while(offset < text.length()) { |
| char ch = text.charAt(offset); |
| if(ch == ' ' || ch == '[' || ch == '.' || ch == '#' || ch == ':') { |
| endRule = offset; |
| offset = text.length(); |
| } |
| offset++; |
| } |
| if (endRule == -1) { |
| endRule = text.length(); |
| } |
| return new Rule(text.substring(startRule, endRule), ts.offset() + startRule, ts.offset() + endRule); |
| } |
| } |
| return null; |
| } |
| |
| //useless class just because we need to put something into the AlternativeLocation to be |
| //able to get some icon from it |
| private static final CssSelectorElementHandle CSS_SELECTOR_ELEMENT_HANDLE_SINGLETON = new CssSelectorElementHandle(); |
| |
| // Note: this class has a natural ordering that is inconsistent with equals. |
| // We have to implement AlternativeLocation |
| @org.netbeans.api.annotations.common.SuppressWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") |
| private static class AlternativeLocationImpl implements AlternativeLocation { |
| |
| private DeclarationLocation location; |
| private EntryHandle entryHandle; |
| private RefactoringElementType type; |
| private static final int SELECTOR_TEXT_MAX_LENGTH = 50; |
| |
| public AlternativeLocationImpl(DeclarationLocation location, EntryHandle entry, RefactoringElementType type) { |
| this.location = location; |
| this.entryHandle = entry; |
| this.type = type; |
| } |
| |
| @Override |
| public ElementHandle getElement() { |
| return CSS_SELECTOR_ELEMENT_HANDLE_SINGLETON; |
| } |
| |
| @Override |
| public String getDisplayHtml(HtmlFormatter formatter) { |
| StringBuilder b = new StringBuilder(); |
| //colorize the 'current line text' a bit |
| //find out if there's the opening curly bracket |
| String lineText = entryHandle.entry().getLineText().toString(); |
| assert lineText != null; |
| int curlyBracketIndex = lineText.indexOf('{'); //NOI18N |
| String croppedLineText = curlyBracketIndex == -1 ? lineText : lineText.substring(0, curlyBracketIndex); |
| |
| //split the text to three parts: the element text itself, its prefix and postfix |
| //then render the element test in bold |
| String elementTextPrefix; |
| switch (type) { |
| case CLASS: |
| elementTextPrefix = "."; //NOI18N |
| break; |
| case ID: |
| elementTextPrefix = "#"; //NOI18N |
| break; |
| default: |
| elementTextPrefix = ""; |
| } |
| String elementText = elementTextPrefix + entryHandle.entry().getName(); |
| int elementTextIndex = croppedLineText.indexOf(elementText); |
| if(elementTextIndex == -1) { |
| String msg = "A parsing error occured when trying to extract display name for html declaration finder." |
| + "elementText='" + elementText |
| + "'; lineText='" + lineText + "'; croppedLineText='" |
| + croppedLineText + "'; elementTextPrefix='" + elementTextPrefix + "'"; //NOI18N |
| Logger.getAnonymousLogger().log(Level.INFO, msg, new IllegalStateException());//NOI18N |
| |
| return entryHandle.entry().getName(); |
| } |
| String prefix = croppedLineText.substring(0, elementTextIndex).trim(); |
| String postfix = croppedLineText.substring(elementTextIndex + elementText.length()).trim(); |
| |
| //now strip the prefix and postfix so the whole text is not longer than SELECTOR_TEXT_MAX_LENGTH |
| int overlap = croppedLineText.length() - SELECTOR_TEXT_MAX_LENGTH; |
| if (overlap > 0) { |
| //strip |
| int stripFromPrefix = Math.min(overlap / 2, prefix.length()); |
| prefix = ".." + prefix.substring(stripFromPrefix); |
| int stripFromPostfix = Math.min(overlap - stripFromPrefix, postfix.length()); |
| postfix = postfix.substring(0, postfix.length() - stripFromPostfix) + ".."; |
| } |
| |
| b.append("<font color=007c00>");//NOI18N |
| b.append(prefix); |
| b.append(' '); //NOI18N |
| b.append("<b>"); //NOI18N |
| b.append(elementText); |
| b.append("</b>"); //NOI18N |
| b.append(' '); //NOI18N |
| b.append(postfix); |
| b.append("</font> in "); //NOI18N |
| |
| //add a link to the file relative to the web root |
| FileObject file = location.getFileObject(); |
| Project project = FileOwnerQuery.getOwner(file); |
| FileObject pathRoot = null; // ProjectWebRootQuery.getWebRoot(file); |
| |
| if (project != null) { |
| pathRoot = project.getProjectDirectory(); |
| } |
| |
| String path = null; |
| String resolveTo = null; |
| if (pathRoot != null) { |
| path = FileUtil.getRelativePath(pathRoot, file); //this may also return null |
| } |
| if (path == null) { |
| //the file cannot be resolved relatively to the webroot or no webroot found |
| //try to resolve relative path to the project's root folder |
| if (project != null) { |
| pathRoot = project.getProjectDirectory(); |
| path = FileUtil.getRelativePath(pathRoot, file); //this may also return null |
| if (path != null) { |
| resolveTo = "${project.home}/"; //NOI18N |
| } |
| } |
| } |
| |
| if (path == null) { |
| //if everything fails, just use the absolute path |
| path = file.getPath(); |
| } |
| |
| if (resolveTo != null) { |
| b.append("<i>"); //NOI18N |
| b.append(resolveTo); |
| b.append("</i>"); //NOI18N |
| } |
| b.append(path); |
| int lineOffset = entryHandle.entry().getLineOffset(); |
| if (lineOffset != -1) { |
| b.append(":"); //NOI18N |
| b.append(lineOffset + 1); //line offsets are counted from zero, but in editor lines starts with one. |
| } |
| return b.toString(); |
| } |
| |
| @Override |
| public DeclarationLocation getLocation() { |
| return location; |
| } |
| |
| @Override |
| public int compareTo(AlternativeLocation o) { |
| //compare according to the file paths |
| return getComparableString(this).compareTo(getComparableString(o)); |
| } |
| |
| private static String getComparableString(AlternativeLocation loc) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(loc.getLocation().getOffset()); //offset |
| FileObject fo = loc.getLocation().getFileObject(); |
| if (fo != null) { |
| sb.append(fo.getPath()); //filename |
| } |
| return sb.toString(); |
| } |
| } |
| |
| private static class CssSelectorElementHandle implements ElementHandle { |
| |
| @Override |
| public FileObject getFileObject() { |
| return null; |
| } |
| |
| @Override |
| public String getMimeType() { |
| return null; |
| } |
| |
| @Override |
| public String getName() { |
| return ""; // NOI18N |
| } |
| |
| @Override |
| public String getIn() { |
| return null; |
| } |
| |
| @Override |
| public ElementKind getKind() { |
| return ElementKind.RULE; |
| } |
| |
| @Override |
| public Set<Modifier> getModifiers() { |
| return Collections.emptySet(); |
| } |
| |
| @Override |
| public boolean signatureEquals(ElementHandle handle) { |
| return false; |
| } |
| |
| @Override |
| public OffsetRange getOffsetRange(ParserResult result) { |
| return OffsetRange.NONE; |
| } |
| } |
| } |