| /* |
| * 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.editor; |
| |
| import com.oracle.js.parser.ir.Block; |
| import com.oracle.js.parser.ir.CallNode; |
| import com.oracle.js.parser.ir.FunctionNode; |
| import com.oracle.js.parser.ir.IdentNode; |
| import com.oracle.js.parser.ir.LexicalContext; |
| import com.oracle.js.parser.ir.LiteralNode; |
| import com.oracle.js.parser.ir.Node; |
| import com.oracle.js.parser.ir.VarNode; |
| import com.oracle.js.parser.ir.visitor.NodeVisitor; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.JTextComponent; |
| import org.netbeans.api.lexer.Token; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.modules.csl.api.KeystrokeHandler; |
| import org.netbeans.modules.csl.api.OffsetRange; |
| import org.netbeans.modules.csl.spi.ParserResult; |
| import org.netbeans.modules.javascript2.lexer.api.JsTokenId; |
| import org.netbeans.modules.javascript2.lexer.api.LexUtilities; |
| import org.netbeans.modules.javascript2.editor.parser.JsParserResult; |
| |
| /** |
| * |
| * @author Petr Pisl |
| */ |
| class JsKeyStrokeHandler implements KeystrokeHandler { |
| |
| public JsKeyStrokeHandler() { |
| } |
| |
| @Override |
| public boolean beforeCharInserted(Document doc, int caretOffset, JTextComponent target, char ch) throws BadLocationException { |
| return false; |
| } |
| |
| @Override |
| public boolean afterCharInserted(Document doc, int caretOffset, JTextComponent target, char ch) throws BadLocationException { |
| return false; |
| } |
| |
| @Override |
| public boolean charBackspaced(Document doc, int caretOffset, JTextComponent target, char ch) throws BadLocationException { |
| return false; |
| } |
| |
| @Override |
| public int beforeBreak(Document doc, int caretOffset, JTextComponent target) throws BadLocationException { |
| return -1; |
| } |
| |
| @Override |
| public OffsetRange findMatching(Document doc, int caretOffset) { |
| return OffsetRange.NONE; |
| } |
| |
| @Override |
| public List<OffsetRange> findLogicalRanges(final ParserResult info, final int caretOffset) { |
| final Set<OffsetRange> ranges = new LinkedHashSet(); |
| if (info instanceof JsParserResult) { |
| final JsParserResult jsParserResult = (JsParserResult) info; |
| FunctionNode root = jsParserResult.getRoot(); |
| final TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(jsParserResult.getSnapshot(), caretOffset); |
| if (root != null && ts != null) { |
| |
| root.accept(new NodeVisitor(new LexicalContext()) { |
| |
| final HashSet<String> referencedFunction = new HashSet(); |
| |
| private OffsetRange getOffsetRange(IdentNode node) { |
| // because the truffle parser doesn't set correctly the finish offset, when there are comments after the indent node |
| return new OffsetRange(node.getStart(), node.getStart() + node.getName().length()); |
| } |
| |
| private OffsetRange getOffsetRange(Node node) { |
| if (node instanceof FunctionNode) { |
| return getOffsetRange((FunctionNode) node); |
| } |
| if (node instanceof IdentNode) { |
| return getOffsetRange((IdentNode)node); |
| } |
| return new OffsetRange(node.getStart(), node.getFinish()); |
| } |
| |
| private OffsetRange getOffsetRange(FunctionNode node) { |
| return new OffsetRange(com.oracle.js.parser.Token.descPosition(node.getFirstToken()), |
| com.oracle.js.parser.Token.descPosition(node.getLastToken()) + com.oracle.js.parser.Token.descLength(node.getLastToken())); |
| } |
| |
| @Override |
| protected boolean enterDefault(Node node) { |
| OffsetRange range = getOffsetRange(node); |
| if (node != null && range.getStart() <= caretOffset && caretOffset <= range.getEnd()) { |
| ranges.add(new OffsetRange(range.getStart(),range.getEnd())); |
| return super.enterDefault(node); |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean enterFunctionNode(FunctionNode node) { |
| OffsetRange fnRange = getOffsetRange(node); |
| if (node.isProgram()) { |
| ranges.add(new OffsetRange(0, jsParserResult.getSnapshot().getText().length())); |
| if (fnRange.getStart() <= caretOffset && caretOffset <= fnRange.getEnd()) { |
| ranges.add(new OffsetRange(fnRange.getStart(), fnRange.getEnd())); |
| } |
| processFunction(node); |
| return false; |
| } |
| |
| if (fnRange.getStart() <= caretOffset && caretOffset <= fnRange.getEnd()) { |
| ranges.add(new OffsetRange(fnRange.getStart(), fnRange.getEnd())); |
| int firstParamOffset = fnRange.getEnd(); |
| int lastParamOffset = -1; |
| for (Node param : node.getParameters()) { |
| OffsetRange paramRange = getOffsetRange(param); |
| if (param.getStart() < firstParamOffset) { |
| firstParamOffset = paramRange.getStart(); |
| } |
| if (param.getFinish() > lastParamOffset) { |
| lastParamOffset = paramRange.getEnd(); |
| } |
| } |
| if (node.getParameters().size() > 1 && firstParamOffset < lastParamOffset && firstParamOffset <= caretOffset && caretOffset <= lastParamOffset) { |
| ranges.add(new OffsetRange(firstParamOffset, lastParamOffset)); |
| for (Node param : node.getParameters()) { |
| param.accept(this); |
| } |
| } |
| if (fnRange.getStart() <= caretOffset && caretOffset <= fnRange.getEnd()) { |
| ranges.add(new OffsetRange(fnRange.getStart(), fnRange.getEnd())); |
| processFunction(node); |
| } |
| } |
| return false; |
| } |
| |
| private void processFunction(FunctionNode node) { |
| Block body = node.getBody(); |
| for (Node statement : body.getStatements()) { |
| OffsetRange stRange = getOffsetRange(statement); |
| if (stRange.getStart() <= caretOffset && caretOffset <= stRange.getEnd()) { |
| statement.accept(this); |
| } |
| } |
| } |
| |
| @Override |
| public boolean enterVarNode(VarNode node) { |
| OffsetRange range = getOffsetRange(node); |
| ts.move(range.getStart()); |
| Token<? extends JsTokenId> token = LexUtilities.findPreviousIncluding(ts, Arrays.asList(JsTokenId.KEYWORD_VAR)); |
| if (token != null && ts.offset() <= caretOffset && caretOffset <= range.getEnd()) { |
| ranges.add(new OffsetRange(ts.offset(), range.getEnd())); |
| return enterDefault(node); |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean enterLiteralNode(LiteralNode node) { |
| if (node.isString() && node.getStart() <= caretOffset && caretOffset <= node.getFinish()) { |
| // include the " or ' |
| ranges.add(new OffsetRange(node.getStart() - 1, node.getFinish() + 1)); |
| } |
| return super.enterLiteralNode(node); |
| } |
| |
| @Override |
| public boolean enterCallNode(CallNode node) { |
| if (node.getArgs().size() > 1) { |
| OffsetRange range = getOffsetRange(node); |
| if (range.getStart() <= caretOffset && caretOffset <= range.getEnd()) { |
| ranges.add(range); |
| int firstArgOffset = node.getFinish(); |
| int lastArgOffset = -1; |
| for (Node arg : node.getArgs()) { |
| OffsetRange argRange = getOffsetRange(arg); |
| if (argRange.getStart() < firstArgOffset) { |
| firstArgOffset = argRange.getStart(); |
| if (arg instanceof LiteralNode && ((LiteralNode)arg).isString()) { |
| firstArgOffset--; |
| } |
| } |
| if (argRange.getEnd() > lastArgOffset) { |
| lastArgOffset = argRange.getEnd(); |
| if (arg instanceof LiteralNode && ((LiteralNode)arg).isString()) { |
| lastArgOffset++; |
| } |
| } |
| } |
| if (firstArgOffset <= caretOffset && caretOffset <= lastArgOffset) { |
| ranges.add(new OffsetRange(firstArgOffset, lastArgOffset)); |
| } |
| for (Node arg : node.getArgs()) { |
| OffsetRange argRange = getOffsetRange(arg); |
| if (argRange.getStart() <= caretOffset && caretOffset <= argRange.getEnd()) { |
| arg.accept(this); |
| } |
| } |
| } |
| return false; |
| } else { |
| return super.enterCallNode(node); |
| } |
| } |
| |
| }); |
| } |
| } |
| |
| final ArrayList<OffsetRange> retval = new ArrayList(ranges); |
| Collections.reverse(retval); |
| return retval; |
| } |
| |
| @Override |
| public int getNextWordOffset(Document doc, int caretOffset, boolean reverse) { |
| return -1; |
| } |
| |
| } |