mirror of
https://github.com/Tooloop/Tooloop-Packages.git
synced 2026-04-28 04:51:38 +02:00
change folder structure and control files to newest concept
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
package antlr;
|
||||
|
||||
/* ANTLR Translator Generator
|
||||
* Project led by Terence Parr at http://www.jGuru.com
|
||||
* Software rights: http://www.antlr.org/RIGHTS.html
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import antlr.collections.*;
|
||||
|
||||
|
||||
/** A CommonAST whose initialization copies hidden token
|
||||
* information from the Token used to create a node.
|
||||
*/
|
||||
public class ExtendedCommonASTWithHiddenTokens
|
||||
extends CommonASTWithHiddenTokens {
|
||||
|
||||
public ExtendedCommonASTWithHiddenTokens() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ExtendedCommonASTWithHiddenTokens(Token tok) {
|
||||
super(tok);
|
||||
}
|
||||
|
||||
public void initialize(AST ast) {
|
||||
ExtendedCommonASTWithHiddenTokens a =
|
||||
(ExtendedCommonASTWithHiddenTokens)ast;
|
||||
super.initialize(a);
|
||||
hiddenBefore = a.getHiddenBefore();
|
||||
hiddenAfter = a.getHiddenAfter();
|
||||
}
|
||||
|
||||
public String getHiddenAfterString() {
|
||||
|
||||
CommonHiddenStreamToken t;
|
||||
StringBuilder hiddenAfterString = new StringBuilder(100);
|
||||
|
||||
for ( t = hiddenAfter ; t != null ; t = t.getHiddenAfter() ) {
|
||||
hiddenAfterString.append(t.getText());
|
||||
}
|
||||
|
||||
return hiddenAfterString.toString();
|
||||
}
|
||||
|
||||
public String getHiddenBeforeString() {
|
||||
|
||||
antlr.CommonHiddenStreamToken
|
||||
child = null,
|
||||
parent = hiddenBefore;
|
||||
|
||||
// if there aren't any hidden tokens here, quietly return
|
||||
//
|
||||
if (parent == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// traverse back to the head of the list of tokens before this node
|
||||
do {
|
||||
child = parent;
|
||||
parent = child.getHiddenBefore();
|
||||
} while (parent != null);
|
||||
|
||||
// dump that list
|
||||
|
||||
StringBuilder hiddenBeforeString = new StringBuilder(100);
|
||||
|
||||
for ( CommonHiddenStreamToken t = child; t != null ;
|
||||
t = t.getHiddenAfter() ) {
|
||||
hiddenBeforeString.append(t.getText());
|
||||
}
|
||||
|
||||
return hiddenBeforeString.toString();
|
||||
}
|
||||
|
||||
public void xmlSerializeNode(Writer out) throws IOException {
|
||||
StringBuilder sb = new StringBuilder(100);
|
||||
sb.append("<");
|
||||
sb.append(getClass().getName() + " ");
|
||||
|
||||
sb.append("hiddenBeforeString=\"" +
|
||||
encode(getHiddenBeforeString()) +
|
||||
"\" text=\"" + encode(getText()) + "\" type=\"" +
|
||||
getType() + "\" hiddenAfterString=\"" +
|
||||
encode(getHiddenAfterString()) + "\"/>");
|
||||
out.write(sb.toString());
|
||||
}
|
||||
|
||||
public void xmlSerializeRootOpen(Writer out) throws IOException {
|
||||
StringBuilder sb = new StringBuilder(100);
|
||||
sb.append("<");
|
||||
sb.append(getClass().getName() + " ");
|
||||
sb.append("hiddenBeforeString=\"" +
|
||||
encode(getHiddenBeforeString()) +
|
||||
"\" text=\"" + encode(getText()) + "\" type=\"" +
|
||||
getType() + "\" hiddenAfterString=\"" +
|
||||
encode(getHiddenAfterString()) + "\">\n");
|
||||
out.write(sb.toString());
|
||||
}
|
||||
|
||||
public void xmlSerializeRootClose(Writer out)
|
||||
throws IOException {
|
||||
out.write("</" + getClass().getName() + ">\n");
|
||||
}
|
||||
|
||||
public void xmlSerialize(Writer out) throws IOException {
|
||||
// print out this node and all siblings
|
||||
for (AST node = this;
|
||||
node != null;
|
||||
node = node.getNextSibling()) {
|
||||
if (node.getFirstChild() == null) {
|
||||
// print guts (class name, attributes)
|
||||
((BaseAST)node).xmlSerializeNode(out);
|
||||
}
|
||||
else {
|
||||
((BaseAST)node).xmlSerializeRootOpen(out);
|
||||
|
||||
// print children
|
||||
((BaseAST)node.getFirstChild()).xmlSerialize(out);
|
||||
|
||||
// print end tag
|
||||
((BaseAST)node).xmlSerializeRootClose(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
package antlr;
|
||||
|
||||
|
||||
import antlr.collections.impl.BitSet;
|
||||
|
||||
/**
|
||||
* This class provides TokenStreamHiddenTokenFilters with the concept of
|
||||
* tokens which can be copied so that they are seen by both the hidden token
|
||||
* stream as well as the parser itself. This is useful when one wants to use
|
||||
* an existing parser (like the Java parser included with ANTLR) that throws
|
||||
* away some tokens to create a parse tree which can be used to spit out
|
||||
* a copy of the code with only minor modifications.
|
||||
*
|
||||
* This code is partially derived from the public domain ANLTR TokenStream
|
||||
*/
|
||||
public class TokenStreamCopyingHiddenTokenFilter
|
||||
extends TokenStreamHiddenTokenFilter
|
||||
implements TokenStream {
|
||||
|
||||
protected BitSet copyMask;
|
||||
CommonHiddenStreamToken hiddenCopy = null;
|
||||
|
||||
public TokenStreamCopyingHiddenTokenFilter(TokenStream input) {
|
||||
super(input);
|
||||
copyMask = new BitSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that all tokens of type tokenType should be copied. The copy
|
||||
* is put in the stream of hidden tokens, and the original is returned in the
|
||||
* stream of normal tokens.
|
||||
*
|
||||
* @param tokenType integer representing the token type to copied
|
||||
*/
|
||||
public void copy(int tokenType) {
|
||||
copyMask.add(tokenType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a clone of the important parts of the given token. Note that this
|
||||
* does NOT copy the hiddenBefore and hiddenAfter fields.
|
||||
*
|
||||
* @param t token to partially clone
|
||||
* @return newly created partial clone
|
||||
*/
|
||||
public CommonHiddenStreamToken partialCloneToken(CommonHiddenStreamToken t) {
|
||||
|
||||
CommonHiddenStreamToken u = new CommonHiddenStreamToken(t.getType(),
|
||||
t.getText());
|
||||
u.setColumn(t.getColumn());
|
||||
u.setLine(t.getLine());
|
||||
u.setFilename(t.getFilename());
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
public void linkAndCopyToken(CommonHiddenStreamToken prev,
|
||||
CommonHiddenStreamToken monitored) {
|
||||
// create a copy of the token in the lookahead for use as hidden token
|
||||
hiddenCopy = partialCloneToken(LA(1));
|
||||
|
||||
// attach copy to the previous token, whether hidden or monitored
|
||||
prev.setHiddenAfter(hiddenCopy);
|
||||
|
||||
// if previous token was hidden, set the hiddenBefore pointer of the
|
||||
// copy to point back to it
|
||||
if (prev != monitored) {
|
||||
hiddenCopy.setHiddenBefore(prev);
|
||||
}
|
||||
|
||||
// we don't want the non-hidden copy to link back to the hidden
|
||||
// copy on the next pass through this function, so we leave
|
||||
// lastHiddenToken alone
|
||||
|
||||
//System.err.println("hidden copy: " + hiddenCopy.toString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void consumeFirst() throws TokenStreamException {
|
||||
consume(); // get first token of input stream
|
||||
|
||||
// Handle situation where hidden or discarded tokens
|
||||
// appear first in input stream
|
||||
CommonHiddenStreamToken p=null;
|
||||
|
||||
// while hidden, copied, or discarded scarf tokens
|
||||
while ( hideMask.member(LA(1).getType()) ||
|
||||
discardMask.member(LA(1).getType()) ||
|
||||
copyMask.member(LA(1).getType()) ) {
|
||||
|
||||
// if we've hit one of the tokens that needs to be copied, we copy it
|
||||
// and then break out of the loop, because the parser needs to see it
|
||||
// too
|
||||
//
|
||||
if (copyMask.member(LA(1).getType())) {
|
||||
|
||||
// copy the token in the lookahead
|
||||
hiddenCopy = partialCloneToken(LA(1));
|
||||
|
||||
// if there's an existing token before this, link that and the
|
||||
// copy together
|
||||
if (p != null) {
|
||||
p.setHiddenAfter(hiddenCopy);
|
||||
hiddenCopy.setHiddenBefore(p); // double-link
|
||||
}
|
||||
|
||||
lastHiddenToken = hiddenCopy;
|
||||
if (firstHidden == null) {
|
||||
firstHidden = hiddenCopy;
|
||||
}
|
||||
|
||||
// we don't want to consume this token, because it also needs to
|
||||
// be passed through to the parser, so break out of the while look
|
||||
// entirely
|
||||
//
|
||||
break;
|
||||
} else if (hideMask.member(LA(1).getType())) {
|
||||
if (p != null) {
|
||||
p.setHiddenAfter(LA(1));
|
||||
LA(1).setHiddenBefore(p); // double-link
|
||||
}
|
||||
p = LA(1);
|
||||
|
||||
lastHiddenToken = p;
|
||||
if (firstHidden == null) {
|
||||
firstHidden = p; // record hidden token if first
|
||||
}
|
||||
}
|
||||
consume();
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the next monitored token.
|
||||
* Test the token following the monitored token.
|
||||
* If following is another monitored token, save it
|
||||
* for the next invocation of nextToken (like a single
|
||||
* lookahead token) and return it then.
|
||||
* If following is unmonitored, nondiscarded (hidden)
|
||||
* channel token, add it to the monitored token.
|
||||
*
|
||||
* Note: EOF must be a monitored Token.
|
||||
*/
|
||||
public Token nextToken() throws TokenStreamException {
|
||||
// handle an initial condition; don't want to get lookahead
|
||||
// token of this splitter until first call to nextToken
|
||||
if (LA(1) == null) {
|
||||
consumeFirst();
|
||||
}
|
||||
|
||||
//System.err.println();
|
||||
|
||||
// we always consume hidden tokens after monitored, thus,
|
||||
// upon entry LA(1) is a monitored token.
|
||||
CommonHiddenStreamToken monitored = LA(1);
|
||||
|
||||
// point to hidden tokens found during last invocation
|
||||
monitored.setHiddenBefore(lastHiddenToken);
|
||||
lastHiddenToken = null;
|
||||
|
||||
// Look for hidden tokens, hook them into list emanating
|
||||
// from the monitored tokens.
|
||||
consume();
|
||||
CommonHiddenStreamToken prev = monitored;
|
||||
|
||||
// deal with as many not-purely-monitored tokens as possible
|
||||
while ( hideMask.member(LA(1).getType()) ||
|
||||
discardMask.member(LA(1).getType()) ||
|
||||
copyMask.member(LA(1).getType()) ) {
|
||||
|
||||
if (copyMask.member(LA(1).getType())) {
|
||||
|
||||
// copy the token and link it backwards
|
||||
if (hiddenCopy != null) {
|
||||
linkAndCopyToken(hiddenCopy, monitored);
|
||||
} else {
|
||||
linkAndCopyToken(prev, monitored);
|
||||
}
|
||||
|
||||
// we now need to parse it as a monitored token, so we return, which
|
||||
// avoids the consume() call at the end of this loop. the next call
|
||||
// will parse it as a monitored token.
|
||||
//System.err.println("returned: " + monitored.toString());
|
||||
return monitored;
|
||||
|
||||
} else if (hideMask.member(LA(1).getType())) {
|
||||
|
||||
// attach the hidden token to the monitored in a chain
|
||||
// link forwards
|
||||
prev.setHiddenAfter(LA(1));
|
||||
|
||||
// link backwards
|
||||
if (prev != monitored) { //hidden cannot point to monitored tokens
|
||||
LA(1).setHiddenBefore(prev);
|
||||
} else if (hiddenCopy != null) {
|
||||
hiddenCopy.setHiddenAfter(LA(1));
|
||||
LA(1).setHiddenBefore(hiddenCopy);
|
||||
hiddenCopy = null;
|
||||
}
|
||||
|
||||
//System.err.println("hidden: " + prev.getHiddenAfter().toString() + "\" after: " + prev.toString());
|
||||
prev = lastHiddenToken = LA(1);
|
||||
}
|
||||
|
||||
consume();
|
||||
}
|
||||
|
||||
// remember the last hidden token for next time around
|
||||
if (hiddenCopy != null) {
|
||||
lastHiddenToken = hiddenCopy;
|
||||
hiddenCopy = null;
|
||||
}
|
||||
|
||||
//System.err.println("returned: " + monitored.toString());
|
||||
return monitored;
|
||||
}
|
||||
}
|
||||
+860
@@ -0,0 +1,860 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Original Copyright (c) 1997, 1998 Van Di-Han HO. All Rights Reserved.
|
||||
Updates Copyright (c) 2001 Jason Pell.
|
||||
Further updates Copyright (c) 2003 Martin Gomez, Ateneo de Manila University
|
||||
Additional updates Copyright (c) 2005-10 Ben Fry and Casey Reas
|
||||
Even more updates Copyright (c) 2014 George Bateman
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, version 2.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java;
|
||||
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import processing.app.Formatter;
|
||||
import processing.app.Preferences;
|
||||
import processing.core.PApplet;
|
||||
|
||||
/**
|
||||
* Handler for dealing with auto format.
|
||||
* Contributed by Martin Gomez, additional bug fixes by Ben Fry.
|
||||
* Additional fixes by Jonathan Feinberg in March 2010.
|
||||
* <p/>
|
||||
* After some further digging, this code in fact appears to be a modified
|
||||
* version of Jason Pell's GPLed "Java Beautifier" class found
|
||||
* <a href="http://web.archive.org/web/20091018220353/http://geocities.com/jasonpell/programs.html">here</a>,
|
||||
* which is itself based on code from Van Di-Han Ho from
|
||||
* <a href="http://web.archive.org/web/20091027043013/http://www.geocities.com/~starkville/vancbj_idx.html">here</a>.
|
||||
* [Ben Fry, August 2009]
|
||||
*/
|
||||
public class AutoFormat implements Formatter {
|
||||
private char[] chars;
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
private final StringBuilder result = new StringBuilder();
|
||||
|
||||
/** The number of spaces in one indent. Constant. */
|
||||
private int indentValue;
|
||||
|
||||
/** Set when the end of the chars array is reached. */
|
||||
private boolean EOF;
|
||||
|
||||
private boolean inStatementFlag; // in a line of code
|
||||
private boolean overflowFlag; // line overrunning?
|
||||
private boolean startFlag; // No buf has yet been writen to this line.
|
||||
private boolean if_flg;
|
||||
private boolean elseFlag;
|
||||
|
||||
/** -1 if not in array or if just after it, otherwise increases from 0. */
|
||||
private int arrayLevel;
|
||||
private int arrayIndent; // Lowest value of the above for this line.
|
||||
|
||||
/** Number of ? entered without exiting at : of a?b:c structures. */
|
||||
private int conditionalLevel;
|
||||
|
||||
private int[][] sp_flg;
|
||||
private boolean[][] s_ind;
|
||||
private int if_lev;
|
||||
|
||||
/** chars[pos] is where we're at. */
|
||||
private int pos;
|
||||
private int level;
|
||||
|
||||
/** Number of curly brackets entered and not exited,
|
||||
excluding arrays. */
|
||||
private int curlyLvl;
|
||||
|
||||
/** Number of parentheses entered and not exited. */
|
||||
private int parenLevel;
|
||||
|
||||
private boolean[] ind;
|
||||
private int[] p_flg;
|
||||
private int[][] s_tabs;
|
||||
|
||||
/** At every {, this has a true pushed if it's a do-while,
|
||||
and a false otherwise. It is then popped at }. */
|
||||
private Stack<Boolean> doWhileFlags;
|
||||
|
||||
/** At every (, this has a true pushed for if, while, or for,
|
||||
and a false otherwise. Popped at ). */
|
||||
private Stack<Boolean> ifWhileForFlags;
|
||||
|
||||
private boolean jdoc_flag;
|
||||
|
||||
/** The number of times to indent at a given point */
|
||||
private int tabs;
|
||||
|
||||
/** The last non-space seen by nextChar(). */
|
||||
private char lastNonWhitespace = 0;
|
||||
|
||||
|
||||
private void handleMultiLineComment() {
|
||||
final boolean savedStartFlag = startFlag;
|
||||
buf.append(nextChar()); // So /*/ isn't self-closing.
|
||||
|
||||
for (char ch = nextChar(); !EOF; ch = nextChar()) {
|
||||
buf.append(ch);
|
||||
while (ch != '/' && !EOF) {
|
||||
if (ch == '\n') {
|
||||
writeIndentedComment();
|
||||
startFlag = true;
|
||||
}
|
||||
buf.append(ch = nextChar());
|
||||
}
|
||||
if (buf.length() >= 2 && buf.charAt(buf.length() - 2) == '*') {
|
||||
jdoc_flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writeIndentedComment();
|
||||
startFlag = savedStartFlag;
|
||||
jdoc_flag = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pumps nextChar into buf until \n or EOF, then calls
|
||||
* writeIndentedLine() and sets startFlag to true.
|
||||
*/
|
||||
private void handleSingleLineComment() {
|
||||
char ch = nextChar();
|
||||
while (ch != '\n' && !EOF) {
|
||||
buf.append(ch);
|
||||
ch = nextChar();
|
||||
}
|
||||
writeIndentedLine();
|
||||
startFlag = true;
|
||||
}
|
||||
|
||||
|
||||
private void writeIndentedLine() {
|
||||
if (buf.length() == 0) {
|
||||
if (startFlag) startFlag = elseFlag = false;
|
||||
return;
|
||||
}
|
||||
if (startFlag) {
|
||||
// Indent suppressed at eg. if<nl>{ and when
|
||||
// buf is close-brackets only followed by ';'.
|
||||
boolean indentMore = !buf.toString().matches("[\\s\\]\\}\\)]+;")
|
||||
&& (buf.charAt(0) != '{' || arrayLevel >= 0)
|
||||
&& overflowFlag;
|
||||
if (indentMore) {
|
||||
tabs++;
|
||||
if (arrayIndent > 0) tabs += arrayIndent;
|
||||
}
|
||||
printIndentation();
|
||||
startFlag = false;
|
||||
if (indentMore) {
|
||||
tabs--;
|
||||
if (arrayIndent > 0) tabs -= arrayIndent;
|
||||
}
|
||||
}
|
||||
if (lastNonSpaceChar() == '}' && bufStarts("else")) {
|
||||
result.append(' ');
|
||||
}
|
||||
|
||||
if (elseFlag) {
|
||||
if (lastNonSpaceChar() == '}') {
|
||||
trimRight(result);
|
||||
result.append(' ');
|
||||
}
|
||||
elseFlag = false;
|
||||
}
|
||||
|
||||
// If we're still in a statement at \n, that's overflow.
|
||||
overflowFlag = inStatementFlag;
|
||||
arrayIndent = arrayLevel;
|
||||
result.append(buf);
|
||||
|
||||
buf.setLength(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the last character in <tt>result</tt> not ' ' or '\n'.
|
||||
*/
|
||||
private char lastNonSpaceChar() {
|
||||
for (int i = result.length() - 1; i >= 0; i--) {
|
||||
char chI = result.charAt(i);
|
||||
if (chI != ' ' && chI != '\n') return chI;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by handleMultilineComment.<br />
|
||||
* Sets jdoc_flag if at the start of a doc comment.
|
||||
* Sends buf to result with proper indents, then clears buf.<br />
|
||||
* Does nothing if buf is empty.
|
||||
*/
|
||||
private void writeIndentedComment() {
|
||||
if (buf.length() == 0) return;
|
||||
|
||||
int firstNonSpace = 0;
|
||||
while (buf.charAt(firstNonSpace) == ' ') firstNonSpace++;
|
||||
if (lookup_com("/**")) jdoc_flag = true;
|
||||
|
||||
if (startFlag) printIndentation();
|
||||
|
||||
if (buf.charAt(firstNonSpace) == '/' && buf.charAt(firstNonSpace+1) == '*') {
|
||||
if (startFlag && lastNonWhitespace != ';') {
|
||||
result.append(buf.substring(firstNonSpace));
|
||||
} else {
|
||||
result.append(buf);
|
||||
}
|
||||
} else {
|
||||
if (buf.charAt(firstNonSpace) == '*' || !jdoc_flag) {
|
||||
result.append(" " + buf.substring(firstNonSpace));
|
||||
} else {
|
||||
result.append(" * " + buf.substring(firstNonSpace));
|
||||
}
|
||||
}
|
||||
buf.setLength(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes tabs >= 0 and appends <tt>tabs*indentValue</tt>
|
||||
* spaces to result.
|
||||
*/
|
||||
private void printIndentation() {
|
||||
if (tabs <= 0) {
|
||||
tabs = 0;
|
||||
return;
|
||||
}
|
||||
final int spaces = tabs * indentValue;
|
||||
for (int i = 0; i < spaces; i++) {
|
||||
result.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return <tt>chars[pos+1]</tt> or '\0' if out-of-bounds.
|
||||
*/
|
||||
private char peek() {
|
||||
return (pos + 1 >= chars.length) ? 0 : chars[pos + 1];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets pos to the position of the next character that is not ' '
|
||||
* in chars. If chars[pos] != ' ' already, it will still move on.
|
||||
* Then sets EOF if pos has reached the end, or reverses pos by 1 if it
|
||||
* has not.
|
||||
* <br/> Does nothing if EOF.
|
||||
* @param allWsp (boolean) Eat newlines too (all of Character.isWhiteSpace()).
|
||||
*/
|
||||
private void advanceToNonSpace(boolean allWsp) {
|
||||
if (EOF) return;
|
||||
|
||||
if (allWsp) {
|
||||
do {
|
||||
pos++;
|
||||
} while (pos < chars.length && Character.isWhitespace(chars[pos]));
|
||||
} else {
|
||||
do {
|
||||
pos++;
|
||||
} while (pos < chars.length && chars[pos] == ' ');
|
||||
}
|
||||
|
||||
if (pos == chars.length - 1) {
|
||||
EOF = true;
|
||||
} else {
|
||||
pos--; // reset for nextChar()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Increments pos, sets EOF if needed, and returns the new
|
||||
* chars[pos] or zero if out-of-bounds.
|
||||
* Sets lastNonWhitespace if chars[pos] isn't whitespace.
|
||||
* Does nothing and returns zero if already at EOF.
|
||||
*/
|
||||
private char nextChar() {
|
||||
if (EOF) return 0;
|
||||
pos++;
|
||||
if (pos >= chars.length - 1) EOF = true;
|
||||
if (pos >= chars.length) return 0;
|
||||
|
||||
char retVal = chars[pos];
|
||||
if (!Character.isWhitespace(retVal)) lastNonWhitespace = retVal;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called after else.
|
||||
*/
|
||||
private void gotElse() {
|
||||
tabs = s_tabs[curlyLvl][if_lev];
|
||||
p_flg[level] = sp_flg[curlyLvl][if_lev];
|
||||
ind[level] = s_ind[curlyLvl][if_lev];
|
||||
if_flg = true;
|
||||
// We can't expect else to be followed by a semicolon, so must
|
||||
// end the statemant manually.
|
||||
inStatementFlag = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pump any '\t' and ' ' to buf, handle any following comment,
|
||||
* and if the next character is '\n', discard it.
|
||||
* @return Whether a '\n' was found and discarded.
|
||||
*/
|
||||
private boolean readForNewLine() {
|
||||
final int savedTabs = tabs;
|
||||
char c = peek();
|
||||
while (!EOF && (c == '\t' || c == ' ')) {
|
||||
buf.append(nextChar());
|
||||
c = peek();
|
||||
}
|
||||
|
||||
if (c == '/') {
|
||||
buf.append(nextChar());
|
||||
c = peek();
|
||||
if (c == '*') {
|
||||
buf.append(nextChar());
|
||||
handleMultiLineComment();
|
||||
} else if (c == '/') {
|
||||
buf.append(nextChar());
|
||||
handleSingleLineComment();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
c = peek();
|
||||
if (c == '\n') {
|
||||
// eat it
|
||||
nextChar();
|
||||
tabs = savedTabs;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return last non-wsp in result+buf, or 0 on error.
|
||||
*/
|
||||
private char prevNonWhitespace() {
|
||||
StringBuffer tot = new StringBuffer();
|
||||
tot.append(result);
|
||||
tot.append(buf);
|
||||
for (int i = tot.length()-1; i >= 0; i--) {
|
||||
if (!Character.isWhitespace(tot.charAt(i)))
|
||||
return tot.charAt(i);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sees if buf is of the form [optional whitespace][keyword][optional anything].
|
||||
* It won't allow keyword to be directly followed by an alphanumeric, _, or &.
|
||||
* Will be different if keyword contains regex codes.
|
||||
*/
|
||||
private boolean bufStarts(final String keyword) {
|
||||
return Pattern.matches("^\\s*" + keyword + "(?![a-zA-Z0-9_&]).*$", buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sees if buf is of the form [optional anything][keyword][optional whitespace].
|
||||
* It won't allow keyword to be directly preceded by an alphanumeric, _, or &.
|
||||
* Will be different if keyword contains regex codes.
|
||||
*/
|
||||
private boolean bufEnds(final String keyword) {
|
||||
return Pattern.matches("^.*(?<![a-zA-Z0-9_&])" + keyword + "\\s*$", buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allows you to increase if_lev safely. Enlarges related arrays where needed;
|
||||
* does not change if_lev itself.
|
||||
*/
|
||||
private void if_levSafe() {
|
||||
if (s_tabs[0].length <= if_lev) {
|
||||
for (int i = 0; i < s_tabs.length; i++) {
|
||||
s_tabs[i] = PApplet.expand(s_tabs[i]);
|
||||
}
|
||||
}
|
||||
if (sp_flg[0].length <= if_lev) {
|
||||
for (int i = 0; i < sp_flg.length; i++) {
|
||||
sp_flg[i] = PApplet.expand(sp_flg[i]);
|
||||
}
|
||||
}
|
||||
if (s_ind[0].length <= if_lev) {
|
||||
for (int i = 0; i < s_ind.length; i++) {
|
||||
s_ind[i] = PApplet.expand(s_ind[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sees if buf is of the form [optional whitespace][keyword][optional anything].
|
||||
* It *will* allow keyword to be directly followed by an alphanumeric, _, or &.
|
||||
* Will be different if keyword contains regex codes (except *, which is fine).
|
||||
*/
|
||||
private boolean lookup_com(final String keyword) {
|
||||
final String regex = "^\\s*" + keyword.replace("*", "\\*") + ".*$";
|
||||
return Pattern.matches(regex, buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes all whitespace off the end of its argument.
|
||||
*/
|
||||
static private void trimRight(final StringBuilder sb) {
|
||||
while (sb.length() >= 1 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
|
||||
sb.setLength(sb.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Entry point */
|
||||
public String format(final String source) {
|
||||
final String normalizedText = source.replaceAll("\r", "");
|
||||
String cleanText = normalizedText;
|
||||
if (!normalizedText.endsWith("\n")) {
|
||||
cleanText += "\n";
|
||||
}
|
||||
|
||||
// Globals' description at top of file.
|
||||
result.setLength(0);
|
||||
indentValue = Preferences.getInteger("editor.tabs.size");
|
||||
|
||||
boolean forFlag = if_flg = false;
|
||||
startFlag = true;
|
||||
int forParenthLevel = 0;
|
||||
conditionalLevel = parenLevel = curlyLvl = if_lev = level = 0;
|
||||
tabs = 0;
|
||||
jdoc_flag = inStatementFlag = overflowFlag = false;
|
||||
pos = arrayLevel = -1;
|
||||
|
||||
int[] s_level = new int[10];
|
||||
sp_flg = new int[20][10];
|
||||
s_ind = new boolean[20][10];
|
||||
int[] s_if_lev = new int[10]; // Stack
|
||||
boolean[] s_if_flg = new boolean[10]; // Stack
|
||||
ind = new boolean[10];
|
||||
p_flg = new int[10];
|
||||
s_tabs = new int[20][10];
|
||||
doWhileFlags = new Stack<>();
|
||||
ifWhileForFlags = new Stack<>();
|
||||
|
||||
chars = cleanText.toCharArray();
|
||||
|
||||
EOF = false; // set in nextChar() when EOF
|
||||
|
||||
while (!EOF) {
|
||||
char c = nextChar();
|
||||
|
||||
switch (c) {
|
||||
default:
|
||||
inStatementFlag = true;
|
||||
buf.append(c);
|
||||
break;
|
||||
|
||||
case ',':
|
||||
inStatementFlag = true;
|
||||
trimRight(buf);
|
||||
buf.append(", ");
|
||||
advanceToNonSpace(false);
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
case '\t':
|
||||
elseFlag = bufEnds("else");
|
||||
if (elseFlag) {
|
||||
gotElse();
|
||||
if (!startFlag || buf.length() > 0) {
|
||||
buf.append(c);
|
||||
}
|
||||
|
||||
writeIndentedLine();
|
||||
startFlag = false;
|
||||
break;
|
||||
}
|
||||
// Only allow in the line, to nuke the old indent.
|
||||
if (!startFlag || buf.length() > 0) buf.append(c);
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
if (EOF) break;
|
||||
|
||||
elseFlag = bufEnds("else");
|
||||
if (elseFlag) gotElse();
|
||||
|
||||
if (lookup_com("//")) {
|
||||
if (buf.charAt(buf.length() - 1) == '\n') {
|
||||
buf.setLength(buf.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (elseFlag) {
|
||||
writeIndentedLine();
|
||||
result.append("\n");
|
||||
|
||||
p_flg[level]++;
|
||||
tabs++;
|
||||
} else {
|
||||
writeIndentedLine();
|
||||
result.append("\n");
|
||||
}
|
||||
startFlag = true;
|
||||
break;
|
||||
|
||||
case '{':
|
||||
elseFlag = bufEnds("else");
|
||||
if (elseFlag) gotElse();
|
||||
|
||||
doWhileFlags.push(Boolean.valueOf(bufEnds("do")));
|
||||
|
||||
char prevChar = prevNonWhitespace();
|
||||
if (arrayLevel >= 0 || prevChar == '=' || prevChar == ']') {
|
||||
// If we're already in an array (lvl >= 0), increment level.
|
||||
// Otherwise, the presence of a = or ] indicates an array is starting
|
||||
// and we should start counting (set lvl=0).
|
||||
arrayLevel++;
|
||||
buf.append(c);
|
||||
break; // Nothing fancy.
|
||||
}
|
||||
|
||||
inStatementFlag = false; // eg. class declaration ends
|
||||
|
||||
if (s_if_lev.length == curlyLvl) {
|
||||
s_if_lev = PApplet.expand(s_if_lev);
|
||||
s_if_flg = PApplet.expand(s_if_flg);
|
||||
}
|
||||
s_if_lev[curlyLvl] = if_lev;
|
||||
s_if_flg[curlyLvl] = if_flg;
|
||||
if_lev = 0;
|
||||
if_flg = false;
|
||||
curlyLvl++;
|
||||
if (startFlag && p_flg[level] != 0) {
|
||||
p_flg[level]--;
|
||||
tabs--;
|
||||
}
|
||||
|
||||
trimRight(buf);
|
||||
if (buf.length() > 0 || (result.length() > 0 &&
|
||||
!Character.isWhitespace(result.charAt(result.length() - 1)))) {
|
||||
buf.append(" ");
|
||||
}
|
||||
buf.append(c);
|
||||
writeIndentedLine();
|
||||
readForNewLine();
|
||||
writeIndentedLine();
|
||||
|
||||
result.append('\n');
|
||||
tabs++;
|
||||
startFlag = true;
|
||||
|
||||
if (p_flg[level] > 0) {
|
||||
ind[level] = true;
|
||||
level++;
|
||||
s_level[level] = curlyLvl;
|
||||
}
|
||||
break;
|
||||
|
||||
case '}':
|
||||
if (arrayLevel >= 0) {
|
||||
// Even less fancy. Note that }s cannot end array behaviour;
|
||||
// a semicolon is needed.
|
||||
if (arrayLevel > 0) arrayLevel--;
|
||||
if (arrayIndent > arrayLevel) arrayIndent = arrayLevel;
|
||||
buf.append(c);
|
||||
break;
|
||||
}
|
||||
|
||||
// In a simple enum, there's not necessarily a `;` to end the statement.
|
||||
inStatementFlag = false;
|
||||
|
||||
curlyLvl--;
|
||||
if (curlyLvl < 0) {
|
||||
curlyLvl = 0;
|
||||
buf.append(c);
|
||||
writeIndentedLine();
|
||||
} else {
|
||||
if_lev = s_if_lev[curlyLvl] - 1;
|
||||
if (if_lev < 0) if_lev = 0;
|
||||
if_levSafe();
|
||||
|
||||
if_flg = s_if_flg[curlyLvl];
|
||||
trimRight(buf);
|
||||
writeIndentedLine();
|
||||
tabs--;
|
||||
|
||||
trimRight(result);
|
||||
result.append('\n');
|
||||
overflowFlag = false; // Would normally be done in writeIndentedLine.
|
||||
printIndentation();
|
||||
result.append(c);
|
||||
if (peek() == ';') result.append(nextChar());
|
||||
|
||||
// doWhileFlags contains a TRUE if and only if the
|
||||
// corresponding { was preceeded (bufEnds) by "do".
|
||||
// Just checking for while would fail on if(){} while(){}.
|
||||
if (doWhileFlags.empty() || !doWhileFlags.pop().booleanValue()
|
||||
|| !new String(chars, pos+1, chars.length-pos-1).trim().startsWith("while")) {
|
||||
readForNewLine();
|
||||
writeIndentedLine();
|
||||
result.append('\n');
|
||||
startFlag = true;
|
||||
} else {
|
||||
// Correct spacing in "} while".
|
||||
result.append(' ');
|
||||
advanceToNonSpace(true);
|
||||
startFlag = false;
|
||||
}
|
||||
|
||||
if (curlyLvl < s_level[level] && level > 0) level--;
|
||||
|
||||
if (ind[level]) {
|
||||
tabs -= p_flg[level];
|
||||
p_flg[level] = 0;
|
||||
ind[level] = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '"':
|
||||
case '“':
|
||||
case '”':
|
||||
case '\'':
|
||||
case '‘':
|
||||
case '’':
|
||||
inStatementFlag = true;
|
||||
char realQuote = c;
|
||||
if (c == '“' || c == '”') realQuote = '"';
|
||||
if (c == '‘' || c == '’') realQuote = '\'';
|
||||
buf.append(realQuote);
|
||||
|
||||
char otherQuote = c;
|
||||
if (c == '“') otherQuote = '”';
|
||||
if (c == '”') otherQuote = '“';
|
||||
if (c == '‘') otherQuote = '’';
|
||||
if (c == '’') otherQuote = '‘';
|
||||
|
||||
char cc = nextChar();
|
||||
// In a proper string, all the quotes tested are c. In a curly-quoted
|
||||
// string, there are three possible end quotes: c, its reverse, and
|
||||
// the correct straight quote.
|
||||
while (!EOF && cc != otherQuote && cc != realQuote && cc != c) {
|
||||
buf.append(cc);
|
||||
if (cc == '\\') {
|
||||
buf.append(cc = nextChar());
|
||||
}
|
||||
|
||||
// Syntax error: unterminated string. Leave \n in nextChar, so it
|
||||
// feeds back into the loop.
|
||||
if (peek() == '\n') break;
|
||||
cc = nextChar();
|
||||
}
|
||||
if (cc == otherQuote || cc == realQuote || cc == c) {
|
||||
buf.append(realQuote);
|
||||
if (readForNewLine()) {
|
||||
// push a newline into the stream
|
||||
chars[pos--] = '\n';
|
||||
}
|
||||
} else {
|
||||
// We've had a syntax error if the string wasn't terminated by EOL/
|
||||
// EOF, just abandon this statement.
|
||||
inStatementFlag = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ';':
|
||||
if (forFlag) {
|
||||
// This is like a comma.
|
||||
trimRight(buf);
|
||||
buf.append("; ");
|
||||
// Not non-whitespace: allow \n.
|
||||
advanceToNonSpace(false);
|
||||
break;
|
||||
}
|
||||
buf.append(c);
|
||||
inStatementFlag = false;
|
||||
writeIndentedLine();
|
||||
if (p_flg[level] > 0 && !ind[level]) {
|
||||
tabs -= p_flg[level];
|
||||
p_flg[level] = 0;
|
||||
}
|
||||
readForNewLine();
|
||||
writeIndentedLine();
|
||||
result.append("\n");
|
||||
startFlag = true;
|
||||
// Array behaviour ends at the end of a statement.
|
||||
arrayLevel = -1;
|
||||
|
||||
if (if_lev > 0) {
|
||||
if (if_flg) {
|
||||
if_lev--;
|
||||
if_flg = false;
|
||||
} else {
|
||||
if_lev = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
buf.append(c);
|
||||
buf.append(nextChar());
|
||||
break;
|
||||
|
||||
case '?':
|
||||
conditionalLevel++;
|
||||
buf.append(c);
|
||||
break;
|
||||
|
||||
case ':':
|
||||
// Java 8 :: operator.
|
||||
if (peek() == ':') {
|
||||
result.append(c).append(nextChar());
|
||||
break;
|
||||
}
|
||||
|
||||
// End a?b:c structures.
|
||||
else if (conditionalLevel > 0) {
|
||||
conditionalLevel--;
|
||||
buf.append(c);
|
||||
break;
|
||||
}
|
||||
|
||||
else if (forFlag) {
|
||||
trimRight(buf);
|
||||
buf.append(" : ");
|
||||
// Not to non-whitespace: allow \n.
|
||||
advanceToNonSpace(false);
|
||||
break;
|
||||
}
|
||||
|
||||
buf.append(c);
|
||||
inStatementFlag = false;
|
||||
arrayLevel = -1; // Unlikely to be needed; just in case.
|
||||
|
||||
// Same format for case, default, and other labels.
|
||||
if (tabs > 0) {
|
||||
tabs--;
|
||||
writeIndentedLine();
|
||||
tabs++;
|
||||
} else {
|
||||
writeIndentedLine();
|
||||
}
|
||||
|
||||
readForNewLine();
|
||||
writeIndentedLine();
|
||||
result.append('\n');
|
||||
startFlag = true;
|
||||
break;
|
||||
|
||||
case '/':
|
||||
final char next = peek();
|
||||
if (next == '/') {
|
||||
// call nextChar to move on.
|
||||
buf.append(c).append(nextChar());
|
||||
handleSingleLineComment();
|
||||
result.append("\n");
|
||||
} else if (next == '*') {
|
||||
if (buf.length() > 0) {
|
||||
writeIndentedLine();
|
||||
}
|
||||
buf.append(c).append(nextChar());
|
||||
handleMultiLineComment();
|
||||
} else {
|
||||
buf.append(c);
|
||||
}
|
||||
break;
|
||||
|
||||
case ')':
|
||||
parenLevel--;
|
||||
|
||||
// If we're further back than the start of a for loop, we've
|
||||
// left it.
|
||||
if (forFlag && forParenthLevel > parenLevel) forFlag = false;
|
||||
|
||||
if (parenLevel < 0) parenLevel = 0;
|
||||
buf.append(c);
|
||||
|
||||
boolean wasIfEtc = !ifWhileForFlags.empty() && ifWhileForFlags.pop().booleanValue();
|
||||
if (wasIfEtc) {
|
||||
inStatementFlag = false;
|
||||
arrayLevel = -1; // This is important as it allows arrays in if statements.
|
||||
}
|
||||
|
||||
writeIndentedLine();
|
||||
// Short-circuiting means readForNewLine is only called for if/while/for;
|
||||
// this is important.
|
||||
if (wasIfEtc && readForNewLine()) {
|
||||
chars[pos] = '\n';
|
||||
pos--; // Make nextChar() return the new '\n'.
|
||||
if (parenLevel == 0) {
|
||||
p_flg[level]++;
|
||||
tabs++;
|
||||
ind[level] = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '(':
|
||||
final boolean isFor = bufEnds("for");
|
||||
final boolean isIf = bufEnds("if");
|
||||
|
||||
if (isFor || isIf || bufEnds("while")) {
|
||||
if (!Character.isWhitespace(buf.charAt(buf.length() - 1))) {
|
||||
buf.append(' ');
|
||||
}
|
||||
ifWhileForFlags.push(true);
|
||||
} else {
|
||||
ifWhileForFlags.push(false);
|
||||
}
|
||||
|
||||
buf.append(c);
|
||||
parenLevel++;
|
||||
|
||||
// isFor says "Is it the start of a for?". If it is, we set forFlag and
|
||||
// forParenthLevel. If it is not parenth_lvl was incremented above and
|
||||
// that's it.
|
||||
if (isFor && !forFlag) {
|
||||
forParenthLevel = parenLevel;
|
||||
forFlag = true;
|
||||
} else if (isIf) {
|
||||
writeIndentedLine();
|
||||
s_tabs[curlyLvl][if_lev] = tabs;
|
||||
sp_flg[curlyLvl][if_lev] = p_flg[level];
|
||||
s_ind[curlyLvl][if_lev] = ind[level];
|
||||
if_lev++;
|
||||
if_levSafe();
|
||||
if_flg = true;
|
||||
}
|
||||
} // end switch
|
||||
} // end while not EOF
|
||||
|
||||
if (buf.length() > 0) writeIndentedLine();
|
||||
|
||||
final String formatted = result.toString();
|
||||
return formatted.equals(cleanText) ? source : formatted;
|
||||
}
|
||||
}
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
Copyright (c) 2008-12 Ben Fry and Casey Reas
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Platform;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.RunnerListener;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.SketchException;
|
||||
import processing.app.Util;
|
||||
import processing.app.contrib.ModeContribution;
|
||||
import processing.core.PApplet;
|
||||
import processing.mode.java.runner.Runner;
|
||||
|
||||
|
||||
/**
|
||||
* Class to handle running Processing from the command line.
|
||||
*
|
||||
* @author fry
|
||||
*/
|
||||
public class Commander implements RunnerListener {
|
||||
static final String helpArg = "--help";
|
||||
// static final String preprocArg = "--preprocess";
|
||||
static final String buildArg = "--build";
|
||||
static final String runArg = "--run";
|
||||
static final String presentArg = "--present";
|
||||
static final String sketchArg = "--sketch=";
|
||||
static final String forceArg = "--force";
|
||||
static final String outputArg = "--output=";
|
||||
static final String exportApplicationArg = "--export";
|
||||
static final String noJavaArg = "--no-java";
|
||||
static final String platformArg = "--platform=";
|
||||
static final String bitsArg = "--bits=";
|
||||
// static final String preferencesArg = "--preferences=";
|
||||
|
||||
static final int HELP = -1;
|
||||
static final int PREPROCESS = 0;
|
||||
static final int BUILD = 1;
|
||||
static final int RUN = 2;
|
||||
static final int PRESENT = 3;
|
||||
// static final int EXPORT_APPLET = 4;
|
||||
static final int EXPORT = 4;
|
||||
|
||||
Sketch sketch;
|
||||
|
||||
PrintStream systemOut;
|
||||
PrintStream systemErr;
|
||||
|
||||
|
||||
static public void main(String[] args) {
|
||||
// Do this early so that error messages go to the console
|
||||
Base.setCommandLine();
|
||||
// init the platform so that prefs and other native code is ready to go
|
||||
Platform.init();
|
||||
// make sure a full JDK is installed
|
||||
//Base.initRequirements();
|
||||
|
||||
// launch command line handler
|
||||
new Commander(args);
|
||||
}
|
||||
|
||||
|
||||
public Commander(String[] args) {
|
||||
String sketchPath = null;
|
||||
File sketchFolder = null;
|
||||
String pdePath = null; // path to the .pde file
|
||||
String outputPath = null;
|
||||
File outputFolder = null;
|
||||
boolean outputSet = false; // set an output folder
|
||||
boolean force = false; // replace that no good output folder
|
||||
// String preferencesPath = null;
|
||||
int platform = PApplet.platform; // default to this platform
|
||||
// int platformBits = Base.getNativeBits();
|
||||
int task = HELP;
|
||||
boolean embedJava = true;
|
||||
|
||||
try {
|
||||
if (Platform.isWindows()) {
|
||||
// On Windows, it needs to use the default system encoding.
|
||||
// https://github.com/processing/processing/issues/1633
|
||||
systemOut = new PrintStream(System.out, true);
|
||||
systemErr = new PrintStream(System.err, true);
|
||||
} else {
|
||||
// On OS X, the output goes as MacRoman or something else useless.
|
||||
// http://code.google.com/p/processing/issues/detail?id=1418
|
||||
// (Not sure about Linux, but this has worked since 2.0)
|
||||
systemOut = new PrintStream(System.out, true, "UTF-8");
|
||||
systemErr = new PrintStream(System.err, true, "UTF-8");
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
int argOffset = 0;
|
||||
for (String arg : args) {
|
||||
argOffset++;
|
||||
|
||||
if (arg.length() == 0) {
|
||||
// ignore it, just the crappy shell script
|
||||
|
||||
} else if (arg.equals(helpArg)) {
|
||||
// mode already set to HELP
|
||||
|
||||
// } else if (arg.equals(preprocArg)) {
|
||||
// task = PREPROCESS;
|
||||
|
||||
} else if (arg.equals(buildArg)) {
|
||||
task = BUILD;
|
||||
break;
|
||||
|
||||
} else if (arg.equals(runArg)) {
|
||||
task = RUN;
|
||||
break;
|
||||
|
||||
} else if (arg.equals(presentArg)) {
|
||||
task = PRESENT;
|
||||
break;
|
||||
|
||||
} else if (arg.equals(exportApplicationArg)) {
|
||||
task = EXPORT;
|
||||
break;
|
||||
|
||||
} else if (arg.equals(noJavaArg)) {
|
||||
embedJava = false;
|
||||
|
||||
} else if (arg.startsWith(platformArg)) {
|
||||
// complainAndQuit("The --platform option has been removed from Processing 2.1.", false);
|
||||
String platformStr = arg.substring(platformArg.length());
|
||||
platform = Platform.getIndex(platformStr);
|
||||
if (platform == -1) {
|
||||
complainAndQuit(platformStr + " should instead be " +
|
||||
"'windows', 'macosx', or 'linux'.", true);
|
||||
}
|
||||
|
||||
} else if (arg.startsWith(bitsArg)) {
|
||||
complainAndQuit("The --bits option has been removed.", false);
|
||||
// String bitsStr = arg.substring(bitsArg.length());
|
||||
// if (bitsStr.equals("32")) {
|
||||
// platformBits = 32;
|
||||
// } else if (bitsStr.equals("64")) {
|
||||
// platformBits = 64;
|
||||
// } else {
|
||||
// complainAndQuit("Bits should be either 32 or 64, not " + bitsStr, true);
|
||||
// }
|
||||
|
||||
} else if (arg.startsWith(sketchArg)) {
|
||||
sketchPath = arg.substring(sketchArg.length());
|
||||
sketchFolder = new File(sketchPath);
|
||||
if (!sketchFolder.exists()) {
|
||||
complainAndQuit(sketchFolder + " does not exist.", false);
|
||||
}
|
||||
File pdeFile = new File(sketchFolder, sketchFolder.getName() + ".pde");
|
||||
if (!pdeFile.exists()) {
|
||||
complainAndQuit("Not a valid sketch folder. " + pdeFile + " does not exist.", true);
|
||||
}
|
||||
pdePath = pdeFile.getAbsolutePath();
|
||||
|
||||
// } else if (arg.startsWith(preferencesArg)) {
|
||||
// preferencesPath = arg.substring(preferencesArg.length());
|
||||
|
||||
} else if (arg.startsWith(outputArg)) {
|
||||
outputSet = true;
|
||||
outputPath = arg.substring(outputArg.length());
|
||||
|
||||
} else if (arg.equals(forceArg)) {
|
||||
force = true;
|
||||
|
||||
} else {
|
||||
complainAndQuit("I don't know anything about " + arg + ".", true);
|
||||
}
|
||||
}
|
||||
String[] sketchArgs = PApplet.subset(args, argOffset);
|
||||
|
||||
// if ((outputPath == null) &&
|
||||
// (task == PREPROCESS || task == BUILD ||
|
||||
// task == RUN || task == PRESENT)) {
|
||||
// complainAndQuit("An output path must be specified when using " +
|
||||
// preprocArg + ", " + buildArg + ", " +
|
||||
// runArg + ", or " + presentArg + ".");
|
||||
// }
|
||||
if (task == HELP) {
|
||||
printCommandLine(systemOut);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
if (outputSet) {
|
||||
if (outputPath == null) {
|
||||
complainAndQuit("An output path must be specified.", true);
|
||||
}
|
||||
|
||||
outputFolder = new File(outputPath);
|
||||
if (outputFolder.exists()) {
|
||||
if (force) {
|
||||
Util.removeDir(outputFolder);
|
||||
} else {
|
||||
complainAndQuit("The output folder already exists. " +
|
||||
"Use --force to remove it.", false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!outputFolder.mkdirs()) {
|
||||
complainAndQuit("Could not create the output folder.", false);
|
||||
}
|
||||
}
|
||||
|
||||
// // run static initialization that grabs all the prefs
|
||||
// // (also pass in a prefs path if that was specified)
|
||||
// if (preferencesPath != null) {
|
||||
// Preferences.init(preferencesPath);
|
||||
// }
|
||||
|
||||
Preferences.init();
|
||||
Base.locateSketchbookFolder();
|
||||
|
||||
if (sketchPath == null) {
|
||||
complainAndQuit("No sketch path specified.", true);
|
||||
|
||||
// } else if (!pdePath.toLowerCase().endsWith(".pde")) {
|
||||
// complainAndQuit("Sketch path must point to the main .pde file.", false);
|
||||
|
||||
} else {
|
||||
|
||||
if (outputSet) {
|
||||
if (outputPath.equals(sketchPath)) {
|
||||
complainAndQuit("The sketch path and output path cannot be identical.", false);
|
||||
}
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
|
||||
// JavaMode javaMode =
|
||||
// new JavaMode(null, Base.getContentFile("modes/java"));
|
||||
JavaMode javaMode = (JavaMode)
|
||||
ModeContribution.load(null, Platform.getContentFile("modes/java"),
|
||||
"processing.mode.java.JavaMode").getMode();
|
||||
try {
|
||||
sketch = new Sketch(pdePath, javaMode);
|
||||
|
||||
if (!outputSet) {
|
||||
outputFolder = sketch.makeTempFolder();
|
||||
}
|
||||
|
||||
if (task == BUILD || task == RUN || task == PRESENT) {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
File srcFolder = new File(outputFolder, "source");
|
||||
String className = build.build(srcFolder, outputFolder, true);
|
||||
// String className = build.build(sketchFolder, outputFolder, true);
|
||||
if (className != null) {
|
||||
success = true;
|
||||
if (task == RUN || task == PRESENT) {
|
||||
Runner runner = new Runner(build, this);
|
||||
if (task == PRESENT) {
|
||||
runner.present(sketchArgs);
|
||||
} else {
|
||||
runner.launch(sketchArgs);
|
||||
}
|
||||
success = !runner.vmReturnedError();
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
} else if (task == EXPORT) {
|
||||
if (outputPath == null) {
|
||||
javaMode.handleExportApplication(sketch);
|
||||
} else {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
build.build(true);
|
||||
if (build != null) {
|
||||
String variant = Platform.getVariant();
|
||||
success = build.exportApplication(outputFolder, platform, variant, embedJava);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!success) { // error already printed
|
||||
System.exit(1);
|
||||
}
|
||||
systemOut.println("Finished.");
|
||||
System.exit(0);
|
||||
|
||||
} catch (SketchException re) {
|
||||
statusError(re);
|
||||
System.exit(1);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void statusNotice(String message) {
|
||||
systemErr.println(message);
|
||||
}
|
||||
|
||||
|
||||
public void statusError(String message) {
|
||||
systemErr.println(message);
|
||||
}
|
||||
|
||||
|
||||
public void statusError(Exception exception) {
|
||||
if (exception instanceof SketchException) {
|
||||
SketchException re = (SketchException) exception;
|
||||
|
||||
int codeIndex = re.getCodeIndex();
|
||||
if (codeIndex != -1) {
|
||||
// format the runner exception like emacs
|
||||
//blah.java:2:10:2:13: Syntax Error: This is a big error message
|
||||
// Emacs doesn't like the double line thing coming from Java
|
||||
// https://github.com/processing/processing/issues/2158
|
||||
String filename = sketch.getCode(codeIndex).getFileName();
|
||||
int line = re.getCodeLine() + 1;
|
||||
int column = re.getCodeColumn() + 1;
|
||||
//if (column == -1) column = 0;
|
||||
// TODO if column not specified, should just select the whole line.
|
||||
// But what's the correct syntax for that?
|
||||
systemErr.println(filename + ":" +
|
||||
line + ":" + column + ":" +
|
||||
line + ":" + column + ":" + " " + re.getMessage());
|
||||
|
||||
} else { // no line number, pass the trace along to the user
|
||||
exception.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void complainAndQuit(String lastWords, boolean schoolEmFirst) {
|
||||
if (schoolEmFirst) {
|
||||
printCommandLine(systemErr);
|
||||
}
|
||||
systemErr.println(lastWords);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
|
||||
static void printCommandLine(PrintStream out) {
|
||||
out.println();
|
||||
out.println("Command line edition for Processing " + Base.getVersionName() + " (Java Mode)");
|
||||
out.println();
|
||||
out.println("--help Show this help text. Congratulations.");
|
||||
out.println();
|
||||
out.println("--sketch=<name> Specify the sketch folder (required)");
|
||||
out.println("--output=<name> Specify the output folder (optional and");
|
||||
out.println(" cannot be the same as the sketch folder.)");
|
||||
out.println();
|
||||
out.println("--force The sketch will not build if the output");
|
||||
out.println(" folder already exists, because the contents");
|
||||
out.println(" will be replaced. This option erases the");
|
||||
out.println(" folder first. Use with extreme caution!");
|
||||
out.println();
|
||||
out.println("--build Preprocess and compile a sketch into .class files.");
|
||||
out.println("--run Preprocess, compile, and run a sketch.");
|
||||
out.println("--present Preprocess, compile, and run a sketch in presentation mode.");
|
||||
out.println();
|
||||
out.println("--export Export an application.");
|
||||
out.println("--no-java Do not embed Java. Use at your own risk!");
|
||||
out.println("--platform Specify the platform (export to application only).");
|
||||
out.println(" Should be one of 'windows', 'macosx', or 'linux'.");
|
||||
// out.println("--bits Must be specified if libraries are used that are");
|
||||
// out.println(" 32- or 64-bit specific such as the OpenGL library.");
|
||||
// out.println(" Otherwise specify 0 or leave it out.");
|
||||
|
||||
out.println();
|
||||
out.println("The --build, --run, --present, or --export must be the final parameter");
|
||||
out.println("passed to Processing. Arguments passed following one of those four will");
|
||||
out.println("be passed through to the sketch itself, and therefore available to the");
|
||||
out.println("sketch via the 'args' field. To pass options understood by PApplet.main(),");
|
||||
out.println("write a custom main() method so that the preprocessor does not add one.");
|
||||
out.println("https://github.com/processing/processing/wiki/Command-Line");
|
||||
|
||||
out.println();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void startIndeterminate() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void stopIndeterminate() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void statusHalt() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isHalted() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-12 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.app.ui.Editor;
|
||||
import processing.core.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
//import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
|
||||
//import org.eclipse.jdt.core.compiler.CompilationProgress;
|
||||
|
||||
|
||||
public class Compiler {
|
||||
|
||||
static HashMap<String, String> importSuggestions;
|
||||
static {
|
||||
importSuggestions = new HashMap<>();
|
||||
importSuggestions.put("Arrays", "java.util.Arrays");
|
||||
importSuggestions.put("Collections", "java.util.Collections");
|
||||
importSuggestions.put("Date", "java.util.Date");
|
||||
importSuggestions.put("Frame", "java.awt.Frame");
|
||||
importSuggestions.put("Iterator", "java.util.Iterator");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compile with ECJ. See http://j.mp/8paifz for documentation.
|
||||
*
|
||||
* @param sketch Sketch object to be compiled, used for placing exceptions
|
||||
* @param buildPath Where the temporary files live and will be built from.
|
||||
* @return true if successful.
|
||||
* @throws SketchException Only if there's a problem. Only then.
|
||||
*/
|
||||
static public boolean compile(JavaBuild build) throws SketchException {
|
||||
|
||||
// This will be filled in if anyone gets angry
|
||||
SketchException exception = null;
|
||||
boolean success = false;
|
||||
|
||||
String baseCommand[] = new String[] {
|
||||
"-g",
|
||||
"-Xemacs",
|
||||
//"-noExit", // not necessary for ecj
|
||||
"-source", "1.7",
|
||||
"-target", "1.7",
|
||||
"-encoding", "utf8",
|
||||
"-classpath", build.getClassPath(),
|
||||
"-nowarn", // we're not currently interested in warnings (works in ecj)
|
||||
"-d", build.getBinFolder().getAbsolutePath() // output the classes in the buildPath
|
||||
};
|
||||
//PApplet.println(baseCommand);
|
||||
|
||||
String[] sourceFiles = Util.listFiles(build.getSrcFolder(), false, ".java");
|
||||
String[] command = PApplet.concat(baseCommand, sourceFiles);
|
||||
//PApplet.println(command);
|
||||
|
||||
try {
|
||||
// Load errors into a local StringBuilder
|
||||
final StringBuilder errorBuffer = new StringBuilder();
|
||||
|
||||
// Create single method dummy writer class to slurp errors from ecj
|
||||
Writer internalWriter = new Writer() {
|
||||
public void write(char[] buf, int off, int len) {
|
||||
errorBuffer.append(buf, off, len);
|
||||
}
|
||||
|
||||
public void flush() { }
|
||||
|
||||
public void close() { }
|
||||
};
|
||||
// Wrap as a PrintWriter since that's what compile() wants
|
||||
PrintWriter writer = new PrintWriter(internalWriter);
|
||||
|
||||
//result = com.sun.tools.javac.Main.compile(command, writer);
|
||||
|
||||
PrintWriter outWriter = new PrintWriter(System.out);
|
||||
|
||||
// Version that's not dynamically loaded
|
||||
//CompilationProgress progress = null;
|
||||
//success = BatchCompiler.compile(command, outWriter, writer, progress);
|
||||
|
||||
// Version that *is* dynamically loaded. First gets the mode class loader
|
||||
// so that it can grab the compiler JAR files from it.
|
||||
ClassLoader loader = build.mode.getClassLoader();
|
||||
try {
|
||||
Class<?> batchClass =
|
||||
Class.forName("org.eclipse.jdt.core.compiler.batch.BatchCompiler", false, loader);
|
||||
Class<?> progressClass =
|
||||
Class.forName("org.eclipse.jdt.core.compiler.CompilationProgress", false, loader);
|
||||
Class<?>[] compileArgs =
|
||||
new Class<?>[] { String[].class, PrintWriter.class, PrintWriter.class, progressClass };
|
||||
Method compileMethod = batchClass.getMethod("compile", compileArgs);
|
||||
success = (Boolean)
|
||||
compileMethod.invoke(null, new Object[] { command, outWriter, writer, null });
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new SketchException("Unknown error inside the compiler.");
|
||||
}
|
||||
|
||||
// Close out the stream for good measure
|
||||
writer.flush();
|
||||
writer.close();
|
||||
|
||||
BufferedReader reader =
|
||||
new BufferedReader(new StringReader(errorBuffer.toString()));
|
||||
//System.err.println(errorBuffer.toString());
|
||||
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
//System.out.println("got line " + line); // debug
|
||||
|
||||
// get first line, which contains file name, line number,
|
||||
// and at least the first line of the error message
|
||||
String errorFormat = "([\\w\\d_]+.java):(\\d+):\\s*(.*):\\s*(.*)\\s*";
|
||||
String[] pieces = PApplet.match(line, errorFormat);
|
||||
//PApplet.println(pieces);
|
||||
|
||||
// if it's something unexpected, die and print the mess to the console
|
||||
if (pieces == null) {
|
||||
exception = new SketchException("Cannot parse error text: " + line);
|
||||
exception.hideStackTrace();
|
||||
// Send out the rest of the error message to the console.
|
||||
System.err.println(line);
|
||||
while ((line = reader.readLine()) != null) {
|
||||
System.err.println(line);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// translate the java filename and line number into a un-preprocessed
|
||||
// location inside a source file or tab in the environment.
|
||||
String dotJavaFilename = pieces[1];
|
||||
// Line numbers are 1-indexed from javac
|
||||
int dotJavaLineIndex = PApplet.parseInt(pieces[2]) - 1;
|
||||
String errorMessage = pieces[4];
|
||||
|
||||
exception = build.placeException(errorMessage,
|
||||
dotJavaFilename,
|
||||
dotJavaLineIndex);
|
||||
|
||||
if (exception == null) {
|
||||
exception = new SketchException(errorMessage);
|
||||
}
|
||||
|
||||
String[] parts = null;
|
||||
|
||||
if (errorMessage.startsWith("The import ") &&
|
||||
errorMessage.endsWith("cannot be resolved")) {
|
||||
// The import poo cannot be resolved
|
||||
//import poo.shoe.blah.*;
|
||||
//String what = errorMessage.substring("The import ".length());
|
||||
String[] m = PApplet.match(errorMessage, "The import (.*) cannot be resolved");
|
||||
//what = what.substring(0, what.indexOf(' '));
|
||||
if (m != null) {
|
||||
// System.out.println("'" + m[1] + "'");
|
||||
if (m[1].equals("processing.xml")) {
|
||||
exception.setMessage("processing.xml no longer exists, this code needs to be updated for 2.0.");
|
||||
System.err.println("The processing.xml library has been replaced " +
|
||||
"with a new 'XML' class that's built-in.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else {
|
||||
exception.setMessage("The package " +
|
||||
"\u201C" + m[1] + "\u201D" +
|
||||
" does not exist. " +
|
||||
"You might be missing a library.");
|
||||
System.err.println("Libraries must be " +
|
||||
"installed in a folder named 'libraries' " +
|
||||
"inside the sketchbook folder " +
|
||||
"(see the Preferences window).");
|
||||
}
|
||||
}
|
||||
|
||||
} else if (errorMessage.endsWith("cannot be resolved to a type")) {
|
||||
// xxx cannot be resolved to a type
|
||||
//xxx c;
|
||||
|
||||
String what = errorMessage.substring(0, errorMessage.indexOf(' '));
|
||||
|
||||
if (what.equals("BFont") ||
|
||||
what.equals("BGraphics") ||
|
||||
what.equals("BImage")) {
|
||||
exception.setMessage(what + " has been replaced with P" + what.substring(1));
|
||||
handleCrustyCode();
|
||||
|
||||
} else {
|
||||
exception.setMessage("Cannot find a class or type " +
|
||||
"named \u201C" + what + "\u201D");
|
||||
|
||||
String suggestion = importSuggestions.get(what);
|
||||
if (suggestion != null) {
|
||||
System.err.println("You may need to add \"import " + suggestion + ";\" to the top of your sketch.");
|
||||
System.err.println("To make sketches more portable, imports that are not part of the Processing API were removed in Processing 2.");
|
||||
System.err.println("See the changes page for more information: https://github.com/processing/processing/wiki/Changes");
|
||||
}
|
||||
}
|
||||
|
||||
} else if (errorMessage.endsWith("cannot be resolved")) {
|
||||
// xxx cannot be resolved
|
||||
//println(xxx);
|
||||
|
||||
String what = errorMessage.substring(0, errorMessage.indexOf(' '));
|
||||
|
||||
if (what.equals("LINE_LOOP") ||
|
||||
what.equals("LINE_STRIP")) {
|
||||
exception.setMessage("LINE_LOOP and LINE_STRIP are not available, " +
|
||||
"please update your code.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (what.equals("framerate")) {
|
||||
exception.setMessage("framerate should be changed to frameRate.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (what.equals("screen")) {
|
||||
exception.setMessage("Change screen.width and screen.height to " +
|
||||
"displayWidth and displayHeight.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (what.equals("screenWidth") ||
|
||||
what.equals("screenHeight")) {
|
||||
exception.setMessage("Change screenWidth and screenHeight to " +
|
||||
"displayWidth and displayHeight.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else {
|
||||
exception.setMessage("Cannot find anything " +
|
||||
"named \u201C" + what + "\u201D");
|
||||
}
|
||||
|
||||
} else if (errorMessage.startsWith("Duplicate")) {
|
||||
// "Duplicate nested type xxx"
|
||||
// "Duplicate local variable xxx"
|
||||
|
||||
} else if (null != (parts = PApplet.match(errorMessage,
|
||||
"literal (\\S*) of type (\\S*) is out of range"))) {
|
||||
if ("int".equals(parts[2])) {
|
||||
exception.setMessage("The type int can't handle numbers that big. Try "
|
||||
+ parts[1] + "L to upgrade to long.");
|
||||
} else {
|
||||
// I'd like to give an essay on BigInteger and BigDecimal, but
|
||||
// this margin is too narrow to contain it.
|
||||
exception.setMessage("Even the type " + parts[2] + " can't handle "
|
||||
+ parts[1] + ". Research big numbers in Java.");
|
||||
}
|
||||
} else {
|
||||
// The method xxx(String) is undefined for the type Temporary_XXXX_XXXX
|
||||
//xxx("blah");
|
||||
// The method xxx(String, int) is undefined for the type Temporary_XXXX_XXXX
|
||||
//xxx("blah", 34);
|
||||
// The method xxx(String, int) is undefined for the type PApplet
|
||||
//PApplet.sub("ding");
|
||||
String undefined =
|
||||
"The method (\\S+\\(.*\\)) is undefined for the type (.*)";
|
||||
parts = PApplet.match(errorMessage, undefined);
|
||||
if (parts != null) {
|
||||
if (parts[1].equals("framerate(int)")) {
|
||||
exception.setMessage("framerate() no longer exists, use frameRate() instead.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (parts[1].equals("push()")) {
|
||||
exception.setMessage("push() no longer exists, use pushMatrix() instead.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else if (parts[1].equals("pop()")) {
|
||||
exception.setMessage("pop() no longer exists, use popMatrix() instead.");
|
||||
handleCrustyCode();
|
||||
|
||||
} else {
|
||||
String mess = "The function " + parts[1] + " does not exist.";
|
||||
exception.setMessage(mess);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exception != null) {
|
||||
// The stack trace just shows that this happened inside the compiler,
|
||||
// which is a red herring. Don't ever show it for compiler stuff.
|
||||
exception.hideStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
String bigSigh = "Error while compiling. (" + e.getMessage() + ")";
|
||||
exception = new SketchException(bigSigh);
|
||||
e.printStackTrace();
|
||||
success = false;
|
||||
}
|
||||
// In case there was something else.
|
||||
if (exception != null) throw exception;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
static protected void handleCrustyCode() {
|
||||
System.err.println("This code needs to be updated " +
|
||||
"for this version of Processing, " +
|
||||
"please read the Changes page on the Wiki.");
|
||||
Editor.showChanges();
|
||||
}
|
||||
|
||||
|
||||
protected int caretColumn(String caretLine) {
|
||||
return caretLine.indexOf("^");
|
||||
}
|
||||
}
|
||||
+1466
File diff suppressed because it is too large
Load Diff
+1443
File diff suppressed because it is too large
Load Diff
+2807
File diff suppressed because it is too large
Load Diff
+421
@@ -0,0 +1,421 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
Copyright (c) 2004-12 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java;
|
||||
|
||||
import processing.app.Preferences;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.syntax.*;
|
||||
import processing.app.ui.Editor;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* Filters key events for tab expansion/indent/etc. This is very old code
|
||||
* that we'd love to replace with a smarter parser/formatter, rather than
|
||||
* continuing to hack this class.
|
||||
*/
|
||||
public class JavaInputHandler extends PdeInputHandler {
|
||||
|
||||
/** ctrl-alt on windows and linux, cmd-alt on mac os x */
|
||||
static final int CTRL_ALT = ActionEvent.ALT_MASK |
|
||||
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
|
||||
|
||||
|
||||
public JavaInputHandler(Editor editor) {
|
||||
super(editor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Intercepts key pressed events for JEditTextArea.
|
||||
* <p/>
|
||||
* Called by JEditTextArea inside processKeyEvent(). Note that this
|
||||
* won't intercept actual characters, because those are fired on
|
||||
* keyTyped().
|
||||
* @return true if the event has been handled (to remove it from the queue)
|
||||
*/
|
||||
public boolean handlePressed(KeyEvent event) {
|
||||
char c = event.getKeyChar();
|
||||
int code = event.getKeyCode();
|
||||
|
||||
Sketch sketch = editor.getSketch();
|
||||
JEditTextArea textarea = editor.getTextArea();
|
||||
|
||||
if ((event.getModifiers() & InputEvent.META_MASK) != 0) {
|
||||
//event.consume(); // does nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((code == KeyEvent.VK_BACK_SPACE) || (code == KeyEvent.VK_TAB) ||
|
||||
(code == KeyEvent.VK_ENTER) || ((c >= 32) && (c < 128))) {
|
||||
sketch.setModified(true);
|
||||
}
|
||||
|
||||
if ((code == KeyEvent.VK_UP) &&
|
||||
((event.getModifiers() & InputEvent.CTRL_MASK) != 0)) {
|
||||
// back up to the last empty line
|
||||
char contents[] = textarea.getText().toCharArray();
|
||||
//int origIndex = textarea.getCaretPosition() - 1;
|
||||
int caretIndex = textarea.getCaretPosition();
|
||||
|
||||
int index = calcLineStart(caretIndex - 1, contents);
|
||||
//System.out.println("line start " + (int) contents[index]);
|
||||
index -= 2; // step over the newline
|
||||
//System.out.println((int) contents[index]);
|
||||
boolean onlySpaces = true;
|
||||
while (index > 0) {
|
||||
if (contents[index] == 10) {
|
||||
if (onlySpaces) {
|
||||
index++;
|
||||
break;
|
||||
} else {
|
||||
onlySpaces = true; // reset
|
||||
}
|
||||
} else if (contents[index] != ' ') {
|
||||
onlySpaces = false;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
// if the first char, index will be -2
|
||||
if (index < 0) index = 0;
|
||||
|
||||
if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
|
||||
textarea.setSelectionStart(caretIndex);
|
||||
textarea.setSelectionEnd(index);
|
||||
} else {
|
||||
textarea.setCaretPosition(index);
|
||||
}
|
||||
event.consume();
|
||||
// return true;
|
||||
|
||||
} else if ((code == KeyEvent.VK_DOWN) &&
|
||||
((event.getModifiers() & InputEvent.CTRL_MASK) != 0)) {
|
||||
char contents[] = textarea.getText().toCharArray();
|
||||
int caretIndex = textarea.getCaretPosition();
|
||||
|
||||
int index = caretIndex;
|
||||
int lineStart = 0;
|
||||
boolean onlySpaces = false; // don't count this line
|
||||
while (index < contents.length) {
|
||||
if (contents[index] == 10) {
|
||||
if (onlySpaces) {
|
||||
index = lineStart; // this is it
|
||||
break;
|
||||
} else {
|
||||
lineStart = index + 1;
|
||||
onlySpaces = true; // reset
|
||||
}
|
||||
} else if (contents[index] != ' ') {
|
||||
onlySpaces = false;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
|
||||
textarea.setSelectionStart(caretIndex);
|
||||
textarea.setSelectionEnd(index);
|
||||
} else {
|
||||
textarea.setCaretPosition(index);
|
||||
}
|
||||
event.consume();
|
||||
// return true;
|
||||
|
||||
} else if (c == 9) {
|
||||
if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
|
||||
// if shift is down, the user always expects an outdent
|
||||
// http://code.google.com/p/processing/issues/detail?id=458
|
||||
editor.handleOutdent();
|
||||
|
||||
} else if (textarea.isSelectionActive()) {
|
||||
editor.handleIndent();
|
||||
|
||||
} else if (Preferences.getBoolean("editor.tabs.expand")) {
|
||||
int tabSize = Preferences.getInteger("editor.tabs.size");
|
||||
textarea.setSelectedText(spaces(tabSize));
|
||||
event.consume();
|
||||
|
||||
} else { // !Preferences.getBoolean("editor.tabs.expand")
|
||||
textarea.setSelectedText("\t");
|
||||
event.consume();
|
||||
}
|
||||
|
||||
} else if (code == 10 || code == 13) { // auto-indent
|
||||
if (Preferences.getBoolean("editor.indent")) {
|
||||
char contents[] = textarea.getText().toCharArray();
|
||||
int tabSize = Preferences.getInteger("editor.tabs.size");
|
||||
|
||||
// this is the previous character
|
||||
// (i.e. when you hit return, it'll be the last character
|
||||
// just before where the newline will be inserted)
|
||||
int origIndex = textarea.getCaretPosition() - 1;
|
||||
|
||||
// if the previous thing is a brace (whether prev line or
|
||||
// up farther) then the correct indent is the number of spaces
|
||||
// on that line + 'indent'.
|
||||
// if the previous line is not a brace, then just use the
|
||||
// identical indentation to the previous line
|
||||
|
||||
// calculate the amount of indent on the previous line
|
||||
// this will be used *only if the prev line is not an indent*
|
||||
int spaceCount = calcSpaceCount(origIndex, contents);
|
||||
|
||||
// If the last character was a left curly brace, then indent.
|
||||
// For 0122, walk backwards a bit to make sure that the there isn't a
|
||||
// curly brace several spaces (or lines) back. Also moved this before
|
||||
// calculating extraCount, since it'll affect that as well.
|
||||
int index2 = origIndex;
|
||||
while ((index2 >= 0) &&
|
||||
Character.isWhitespace(contents[index2])) {
|
||||
index2--;
|
||||
}
|
||||
if (index2 != -1) {
|
||||
// still won't catch a case where prev stuff is a comment
|
||||
if (contents[index2] == '{') {
|
||||
// intermediate lines be damned,
|
||||
// use the indent for this line instead
|
||||
spaceCount = calcSpaceCount(index2, contents);
|
||||
spaceCount += tabSize;
|
||||
}
|
||||
}
|
||||
|
||||
// now before inserting this many spaces, walk forward from
|
||||
// the caret position and count the number of spaces,
|
||||
// so that the number of spaces aren't duplicated again
|
||||
int index = origIndex + 1;
|
||||
int extraCount = 0;
|
||||
while ((index < contents.length) &&
|
||||
(contents[index] == ' ')) {
|
||||
//spaceCount--;
|
||||
extraCount++;
|
||||
index++;
|
||||
}
|
||||
int braceCount = 0;
|
||||
while ((index < contents.length) && (contents[index] != '\n')) {
|
||||
if (contents[index] == '}') {
|
||||
braceCount++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
// Hitting return on a line with spaces *after* the caret
|
||||
// can cause trouble. For 0099, was ignoring the case, but this is
|
||||
// annoying, so in 0122 we're trying to fix that.
|
||||
spaceCount -= extraCount;
|
||||
|
||||
if (spaceCount < 0) {
|
||||
// for rev 0122, actually delete extra space
|
||||
//textarea.setSelectionStart(origIndex + 1);
|
||||
textarea.setSelectionEnd(textarea.getSelectionStop() - spaceCount);
|
||||
textarea.setSelectedText("\n");
|
||||
textarea.setCaretPosition(textarea.getCaretPosition() + extraCount + spaceCount);
|
||||
} else {
|
||||
String insertion = "\n" + spaces(spaceCount);
|
||||
textarea.setSelectedText(insertion);
|
||||
textarea.setCaretPosition(textarea.getCaretPosition() + extraCount);
|
||||
}
|
||||
|
||||
// not gonna bother handling more than one brace
|
||||
if (braceCount > 0) {
|
||||
int sel = textarea.getSelectionStart();
|
||||
// sel - tabSize will be -1 if start/end parens on the same line
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=484
|
||||
if (sel - tabSize >= 0) {
|
||||
textarea.select(sel - tabSize, sel);
|
||||
String s = spaces(tabSize);
|
||||
// if these are spaces that we can delete
|
||||
if (textarea.getSelectedText().equals(s)) {
|
||||
textarea.setSelectedText("");
|
||||
} else {
|
||||
textarea.select(sel, sel);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Enter/Return was being consumed by somehow even if false
|
||||
// was returned, so this is a band-aid to simply fire the event again.
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=1073
|
||||
textarea.setSelectedText(String.valueOf(c));
|
||||
}
|
||||
// mark this event as already handled (all but ignored)
|
||||
event.consume();
|
||||
// return true;
|
||||
|
||||
} else if (c == '}') {
|
||||
if (Preferences.getBoolean("editor.indent")) {
|
||||
// first remove anything that was there (in case this multiple
|
||||
// characters are selected, so that it's not in the way of the
|
||||
// spaces for the auto-indent
|
||||
if (textarea.getSelectionStart() != textarea.getSelectionStop()) {
|
||||
textarea.setSelectedText("");
|
||||
}
|
||||
|
||||
// if this brace is the only thing on the line, outdent
|
||||
char contents[] = textarea.getText().toCharArray();
|
||||
// index to the character to the left of the caret
|
||||
int prevCharIndex = textarea.getCaretPosition() - 1;
|
||||
|
||||
// backup from the current caret position to the last newline,
|
||||
// checking for anything besides whitespace along the way.
|
||||
// if there's something besides whitespace, exit without
|
||||
// messing any sort of indenting.
|
||||
int index = prevCharIndex;
|
||||
boolean finished = false;
|
||||
while ((index != -1) && (!finished)) {
|
||||
if (contents[index] == 10) {
|
||||
finished = true;
|
||||
index++;
|
||||
} else if (contents[index] != ' ') {
|
||||
// don't do anything, this line has other stuff on it
|
||||
return false;
|
||||
} else {
|
||||
index--;
|
||||
}
|
||||
}
|
||||
if (!finished) return false; // brace with no start
|
||||
int lineStartIndex = index;
|
||||
|
||||
int pairedSpaceCount = calcBraceIndent(prevCharIndex, contents); //, 1);
|
||||
if (pairedSpaceCount == -1) return false;
|
||||
|
||||
textarea.setSelectionStart(lineStartIndex);
|
||||
textarea.setSelectedText(spaces(pairedSpaceCount));
|
||||
|
||||
// mark this event as already handled
|
||||
event.consume();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean handleTyped(KeyEvent event) {
|
||||
char c = event.getKeyChar();
|
||||
|
||||
if ((event.getModifiers() & InputEvent.CTRL_MASK) != 0) {
|
||||
// on linux, ctrl-comma (prefs) being passed through to the editor
|
||||
if (c == KeyEvent.VK_COMMA) {
|
||||
event.consume();
|
||||
return true;
|
||||
}
|
||||
// https://github.com/processing/processing/issues/3847
|
||||
if (c == KeyEvent.VK_SPACE) {
|
||||
event.consume();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the index for the first character on this line.
|
||||
*/
|
||||
protected int calcLineStart(int index, char contents[]) {
|
||||
// backup from the current caret position to the last newline,
|
||||
// so that we can figure out how far this line was indented
|
||||
/*int spaceCount = 0;*/
|
||||
boolean finished = false;
|
||||
while ((index != -1) && (!finished)) {
|
||||
if ((contents[index] == 10) ||
|
||||
(contents[index] == 13)) {
|
||||
finished = true;
|
||||
//index++; // maybe ?
|
||||
} else {
|
||||
index--; // new
|
||||
}
|
||||
}
|
||||
// add one because index is either -1 (the start of the document)
|
||||
// or it's the newline character for the previous line
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the number of spaces on this line.
|
||||
*/
|
||||
protected int calcSpaceCount(int index, char contents[]) {
|
||||
index = calcLineStart(index, contents);
|
||||
|
||||
int spaceCount = 0;
|
||||
// now walk forward and figure out how many spaces there are
|
||||
while ((index < contents.length) && (index >= 0) &&
|
||||
(contents[index++] == ' ')) {
|
||||
spaceCount++;
|
||||
}
|
||||
return spaceCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Walk back from 'index' until the brace that seems to be
|
||||
* the beginning of the current block, and return the number of
|
||||
* spaces found on that line.
|
||||
*/
|
||||
protected int calcBraceIndent(int index, char[] contents) {
|
||||
// now that we know things are ok to be indented, walk
|
||||
// backwards to the last { to see how far its line is indented.
|
||||
// this isn't perfect cuz it'll pick up commented areas,
|
||||
// but that's not really a big deal and can be fixed when
|
||||
// this is all given a more complete (proper) solution.
|
||||
int braceDepth = 1;
|
||||
boolean finished = false;
|
||||
while ((index != -1) && (!finished)) {
|
||||
if (contents[index] == '}') {
|
||||
// aww crap, this means we're one deeper
|
||||
// and will have to find one more extra {
|
||||
braceDepth++;
|
||||
//if (braceDepth == 0) {
|
||||
//finished = true;
|
||||
//}
|
||||
index--;
|
||||
} else if (contents[index] == '{') {
|
||||
braceDepth--;
|
||||
if (braceDepth == 0) {
|
||||
finished = true;
|
||||
}
|
||||
index--;
|
||||
} else {
|
||||
index--;
|
||||
}
|
||||
}
|
||||
// never found a proper brace, be safe and don't do anything
|
||||
if (!finished) return -1;
|
||||
|
||||
// check how many spaces on the line with the matching open brace
|
||||
//int pairedSpaceCount = calcSpaceCount(index, contents);
|
||||
return calcSpaceCount(index, contents);
|
||||
}
|
||||
|
||||
|
||||
static private String spaces(int count) {
|
||||
char[] c = new char[count];
|
||||
Arrays.fill(c, ' ');
|
||||
return new String(c);
|
||||
}
|
||||
}
|
||||
+456
@@ -0,0 +1,456 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2010-11 Ben Fry and Casey Reas
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.app.ui.Editor;
|
||||
import processing.app.ui.EditorException;
|
||||
import processing.app.ui.EditorState;
|
||||
import processing.mode.java.runner.Runner;
|
||||
import processing.mode.java.tweak.SketchParser;
|
||||
|
||||
|
||||
public class JavaMode extends Mode {
|
||||
|
||||
public Editor createEditor(Base base, String path,
|
||||
EditorState state) throws EditorException {
|
||||
return new JavaEditor(base, path, state, this);
|
||||
}
|
||||
|
||||
|
||||
public JavaMode(Base base, File folder) {
|
||||
super(base, folder);
|
||||
|
||||
// initLogger();
|
||||
loadPreferences();
|
||||
}
|
||||
|
||||
|
||||
public String getTitle() {
|
||||
return "Java";
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
public File[] getExampleCategoryFolders() {
|
||||
return new File[] {
|
||||
new File(examplesFolder, "Basics"),
|
||||
new File(examplesFolder, "Topics"),
|
||||
new File(examplesFolder, "Demos"),
|
||||
new File(examplesFolder, "Books")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public String getDefaultExtension() {
|
||||
return "pde";
|
||||
}
|
||||
|
||||
|
||||
public String[] getExtensions() {
|
||||
return new String[] { "pde", "java" };
|
||||
}
|
||||
|
||||
|
||||
public String[] getIgnorable() {
|
||||
return new String[] {
|
||||
"applet",
|
||||
"application.macosx",
|
||||
"application.windows",
|
||||
"application.linux"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Library getCoreLibrary() {
|
||||
if (coreLibrary == null) {
|
||||
File coreFolder = Platform.getContentFile("core");
|
||||
coreLibrary = new Library(coreFolder);
|
||||
// try {
|
||||
// coreLibrary = getLibrary("processing.core");
|
||||
// System.out.println("core found at " + coreLibrary.getLibraryPath());
|
||||
// } catch (SketchException e) {
|
||||
// Base.log("Serious problem while locating processing.core", e);
|
||||
// }
|
||||
}
|
||||
return coreLibrary;
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
|
||||
/** Handles the standard Java "Run" or "Present" */
|
||||
public Runner handleLaunch(Sketch sketch, RunnerListener listener,
|
||||
final boolean present) throws SketchException {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
// String appletClassName = build.build(false);
|
||||
String appletClassName = build.build(true);
|
||||
if (appletClassName != null) {
|
||||
final Runner runtime = new Runner(build, listener);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
// these block until finished
|
||||
if (present) {
|
||||
runtime.present(null);
|
||||
} else {
|
||||
runtime.launch(null);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
return runtime;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/** Start a sketch in tweak mode */
|
||||
public Runner handleTweak(Sketch sketch,
|
||||
RunnerListener listener) throws SketchException {
|
||||
// final boolean present) throws SketchException {
|
||||
final JavaEditor editor = (JavaEditor) listener;
|
||||
|
||||
// first try to build the unmodified code
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
// String appletClassName = build.build(false);
|
||||
String appletClassName = build.build(true);
|
||||
if (appletClassName == null) {
|
||||
// unmodified build failed, so fail
|
||||
return null;
|
||||
}
|
||||
|
||||
// if compilation passed, modify the code and build again
|
||||
// save the original sketch code of the user
|
||||
editor.initBaseCode();
|
||||
// check for "// tweak" comment in the sketch
|
||||
boolean requiresTweak = SketchParser.containsTweakComment(editor.baseCode);
|
||||
// parse the saved sketch to get all (or only with "//tweak" comment) numbers
|
||||
final SketchParser parser = new SketchParser(editor.baseCode, requiresTweak);
|
||||
|
||||
// add our code to the sketch
|
||||
final boolean launchInteractive = editor.automateSketch(sketch, parser);
|
||||
|
||||
build = new JavaBuild(sketch);
|
||||
appletClassName = build.build(false);
|
||||
|
||||
if (appletClassName != null) {
|
||||
final Runner runtime = new Runner(build, listener);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
// these block until finished
|
||||
// if (present) {
|
||||
// runtime.present(null);
|
||||
// } else {
|
||||
runtime.launch(null);
|
||||
// }
|
||||
// next lines are executed when the sketch quits
|
||||
if (launchInteractive) {
|
||||
// fix swing deadlock issue: https://github.com/processing/processing/issues/3928
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
editor.initEditorCode(parser.allHandles, false);
|
||||
editor.stopTweakMode(parser.allHandles);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
if (launchInteractive) {
|
||||
// fix swing deadlock issue: https://github.com/processing/processing/issues/3928
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
// replace editor code with baseCode
|
||||
editor.initEditorCode(parser.allHandles, false);
|
||||
editor.updateInterface(parser.allHandles, parser.colorBoxes);
|
||||
editor.startTweakMode();
|
||||
}
|
||||
});
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
// TODO Why is this necessary? Why isn't Sketch.isModified() used?
|
||||
static private boolean isSketchModified(Sketch sketch) {
|
||||
for (SketchCode sc : sketch.getCode()) {
|
||||
if (sc.isModified()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// public void handleStop() {
|
||||
// if (runtime != null) {
|
||||
// runtime.close(); // kills the window
|
||||
// runtime = null; // will this help?
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// public boolean handleExportApplet(Sketch sketch) throws SketchException, IOException {
|
||||
// JavaBuild build = new JavaBuild(sketch);
|
||||
// return build.exportApplet();
|
||||
// }
|
||||
|
||||
|
||||
public boolean handleExportApplication(Sketch sketch) throws SketchException, IOException {
|
||||
JavaBuild build = new JavaBuild(sketch);
|
||||
return build.exportApplication();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Any modes that extend JavaMode can override this method to add additional
|
||||
* JARs to be included in the classpath for code completion and error checking
|
||||
* @return searchPath: file-paths separated by File.pathSeparatorChar
|
||||
*/
|
||||
public String getSearchPath() {
|
||||
return getCoreLibrary().getJarPath();
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
// Merged from ExperimentalMode
|
||||
|
||||
/*
|
||||
void initLogger() {
|
||||
final boolean VERBOSE_LOGGING = true;
|
||||
final int LOG_SIZE = 512 * 1024; // max log file size (in bytes)
|
||||
|
||||
Logger globalLogger = Logger.getLogger("");
|
||||
//Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); // doesn't work on os x
|
||||
if (VERBOSE_LOGGING) {
|
||||
globalLogger.setLevel(Level.INFO);
|
||||
} else {
|
||||
globalLogger.setLevel(Level.WARNING);
|
||||
}
|
||||
|
||||
// enable logging to file
|
||||
try {
|
||||
// settings is writable for built-in modes, mode folder is not writable
|
||||
File logFolder = Base.getSettingsFile("debug");
|
||||
if (!logFolder.exists()) {
|
||||
logFolder.mkdir();
|
||||
}
|
||||
File logFile = new File(logFolder, "DebugMode.%g.log");
|
||||
Handler handler = new FileHandler(logFile.getAbsolutePath(), LOG_SIZE, 10, false);
|
||||
globalLogger.addHandler(handler);
|
||||
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(JavaMode.class.getName()).log(Level.SEVERE, null, ex);
|
||||
} catch (SecurityException ex) {
|
||||
Logger.getLogger(JavaMode.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
//ImageIcon classIcon, fieldIcon, methodIcon, localVarIcon;
|
||||
|
||||
// protected void loadIcons() {
|
||||
// String iconPath = getContentFile("data").getAbsolutePath() + File.separator + "icons";
|
||||
// classIcon = new ImageIcon(iconPath + File.separator + "class_obj.png");
|
||||
// methodIcon = new ImageIcon(iconPath + File.separator + "methpub_obj.png");
|
||||
// fieldIcon = new ImageIcon(iconPath + File.separator + "field_protected_obj.png");
|
||||
// localVarIcon = new ImageIcon(iconPath + File.separator + "field_default_obj.png");
|
||||
// }
|
||||
|
||||
|
||||
static public volatile boolean errorCheckEnabled = true;
|
||||
static public volatile boolean warningsEnabled = true;
|
||||
static public volatile boolean codeCompletionsEnabled = true;
|
||||
static public volatile boolean debugOutputEnabled = false;
|
||||
static public volatile boolean errorLogsEnabled = false;
|
||||
static public volatile boolean autoSaveEnabled = true;
|
||||
static public volatile boolean autoSavePromptEnabled = true;
|
||||
static public volatile boolean defaultAutoSaveEnabled = true;
|
||||
static public volatile boolean ccTriggerEnabled = false;
|
||||
static public volatile boolean importSuggestEnabled = true;
|
||||
static public volatile boolean inspectModeHotkeyEnabled = true;
|
||||
static public int autoSaveInterval = 3; //in minutes
|
||||
|
||||
|
||||
/**
|
||||
* After how many typed characters, code completion is triggered
|
||||
*/
|
||||
volatile public static int codeCompletionTriggerLength = 1;
|
||||
|
||||
static public final String prefErrorCheck = "pdex.errorCheckEnabled";
|
||||
static public final String prefWarnings = "pdex.warningsEnabled";
|
||||
static public final String prefDebugOP = "pdex.dbgOutput";
|
||||
static public final String prefErrorLogs = "pdex.writeErrorLogs";
|
||||
static public final String prefAutoSaveInterval = "pdex.autoSaveInterval";
|
||||
static public final String prefAutoSave = "pdex.autoSave.autoSaveEnabled";
|
||||
static public final String prefAutoSavePrompt = "pdex.autoSave.promptDisplay";
|
||||
static public final String prefDefaultAutoSave = "pdex.autoSave.autoSaveByDefault";
|
||||
static public final String suggestionsFileName = "suggestions.txt";
|
||||
|
||||
static public final String COMPLETION_PREF = "pdex.completion";
|
||||
static public final String COMPLETION_TRIGGER_PREF = "pdex.completion.trigger";
|
||||
static public final String SUGGEST_IMPORTS_PREF = "pdex.suggest.imports";
|
||||
static public final String INSPECT_MODE_HOTKEY_PREF = "pdex.inspectMode.hotkey";
|
||||
|
||||
// static volatile public boolean enableTweak = false;
|
||||
|
||||
/**
|
||||
* Stores the white list/black list of allowed/blacklisted imports. These are defined in
|
||||
* suggestions.txt in java mode folder.
|
||||
*/
|
||||
static public final Map<String, Set<String>> suggestionsMap = new HashMap<>();
|
||||
|
||||
public void loadPreferences() {
|
||||
Messages.log("Load PDEX prefs");
|
||||
ensurePrefsExist();
|
||||
errorCheckEnabled = Preferences.getBoolean(prefErrorCheck);
|
||||
warningsEnabled = Preferences.getBoolean(prefWarnings);
|
||||
codeCompletionsEnabled = Preferences.getBoolean(COMPLETION_PREF);
|
||||
// DEBUG = Preferences.getBoolean(prefDebugOP);
|
||||
errorLogsEnabled = Preferences.getBoolean(prefErrorLogs);
|
||||
autoSaveInterval = Preferences.getInteger(prefAutoSaveInterval);
|
||||
// untitledAutoSaveEnabled = Preferences.getBoolean(prefUntitledAutoSave);
|
||||
autoSaveEnabled = Preferences.getBoolean(prefAutoSave);
|
||||
autoSavePromptEnabled = Preferences.getBoolean(prefAutoSavePrompt);
|
||||
defaultAutoSaveEnabled = Preferences.getBoolean(prefDefaultAutoSave);
|
||||
ccTriggerEnabled = Preferences.getBoolean(COMPLETION_TRIGGER_PREF);
|
||||
importSuggestEnabled = Preferences.getBoolean(SUGGEST_IMPORTS_PREF);
|
||||
inspectModeHotkeyEnabled = Preferences.getBoolean(INSPECT_MODE_HOTKEY_PREF);
|
||||
loadSuggestionsMap();
|
||||
}
|
||||
|
||||
|
||||
public void savePreferences() {
|
||||
Messages.log("Saving PDEX prefs");
|
||||
Preferences.setBoolean(prefErrorCheck, errorCheckEnabled);
|
||||
Preferences.setBoolean(prefWarnings, warningsEnabled);
|
||||
Preferences.setBoolean(COMPLETION_PREF, codeCompletionsEnabled);
|
||||
// Preferences.setBoolean(prefDebugOP, DEBUG);
|
||||
Preferences.setBoolean(prefErrorLogs, errorLogsEnabled);
|
||||
Preferences.setInteger(prefAutoSaveInterval, autoSaveInterval);
|
||||
// Preferences.setBoolean(prefUntitledAutoSave,untitledAutoSaveEnabled);
|
||||
Preferences.setBoolean(prefAutoSave, autoSaveEnabled);
|
||||
Preferences.setBoolean(prefAutoSavePrompt, autoSavePromptEnabled);
|
||||
Preferences.setBoolean(prefDefaultAutoSave, defaultAutoSaveEnabled);
|
||||
Preferences.setBoolean(COMPLETION_TRIGGER_PREF, ccTriggerEnabled);
|
||||
Preferences.setBoolean(SUGGEST_IMPORTS_PREF, importSuggestEnabled);
|
||||
Preferences.setBoolean(INSPECT_MODE_HOTKEY_PREF, inspectModeHotkeyEnabled);
|
||||
}
|
||||
|
||||
public void loadSuggestionsMap() {
|
||||
File suggestionsListFile = new File(getFolder() + File.separator
|
||||
+ suggestionsFileName);
|
||||
if (!suggestionsListFile.exists()) {
|
||||
Messages.loge("Suggestions file not found! "
|
||||
+ suggestionsListFile.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(
|
||||
new FileReader(suggestionsListFile));
|
||||
while (true) {
|
||||
String line = br.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
line = line.trim();
|
||||
if (line.startsWith("#")) {
|
||||
continue;
|
||||
} else {
|
||||
if (line.contains("=")) {
|
||||
String key = line.split("=")[0];
|
||||
String val = line.split("=")[1];
|
||||
if (suggestionsMap.containsKey(key)) {
|
||||
suggestionsMap.get(key).add(val);
|
||||
} else {
|
||||
HashSet<String> set = new HashSet<>();
|
||||
set.add(val);
|
||||
suggestionsMap.put(key, set);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
} catch (IOException e) {
|
||||
Messages.loge("IOException while reading suggestions file:"
|
||||
+ suggestionsListFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void ensurePrefsExist() {
|
||||
//TODO: Need to do a better job of managing prefs. Think lists.
|
||||
if (Preferences.get(prefErrorCheck) == null)
|
||||
Preferences.setBoolean(prefErrorCheck, errorCheckEnabled);
|
||||
if (Preferences.get(prefWarnings) == null)
|
||||
Preferences.setBoolean(prefWarnings, warningsEnabled);
|
||||
if (Preferences.get(COMPLETION_PREF) == null)
|
||||
Preferences.setBoolean(COMPLETION_PREF, codeCompletionsEnabled);
|
||||
if (Preferences.get(prefDebugOP) == null)
|
||||
// Preferences.setBoolean(prefDebugOP, DEBUG);
|
||||
if (Preferences.get(prefErrorLogs) == null)
|
||||
Preferences.setBoolean(prefErrorLogs, errorLogsEnabled);
|
||||
if (Preferences.get(prefAutoSaveInterval) == null)
|
||||
Preferences.setInteger(prefAutoSaveInterval, autoSaveInterval);
|
||||
// if(Preferences.get(prefUntitledAutoSave) == null)
|
||||
// Preferences.setBoolean(prefUntitledAutoSave,untitledAutoSaveEnabled);
|
||||
if (Preferences.get(prefAutoSave) == null)
|
||||
Preferences.setBoolean(prefAutoSave, autoSaveEnabled);
|
||||
if (Preferences.get(prefAutoSavePrompt) == null)
|
||||
Preferences.setBoolean(prefAutoSavePrompt, autoSavePromptEnabled);
|
||||
if (Preferences.get(prefDefaultAutoSave) == null)
|
||||
Preferences.setBoolean(prefDefaultAutoSave, defaultAutoSaveEnabled);
|
||||
if (Preferences.get(COMPLETION_TRIGGER_PREF) == null)
|
||||
Preferences.setBoolean(COMPLETION_TRIGGER_PREF, ccTriggerEnabled);
|
||||
if (Preferences.get(SUGGEST_IMPORTS_PREF) == null)
|
||||
Preferences.setBoolean(SUGGEST_IMPORTS_PREF, importSuggestEnabled);
|
||||
if (Preferences.get(INSPECT_MODE_HOTKEY_PREF) == null)
|
||||
Preferences.setBoolean(INSPECT_MODE_HOTKEY_PREF, inspectModeHotkeyEnabled);
|
||||
}
|
||||
|
||||
|
||||
static public void main(String[] args) {
|
||||
processing.app.Base.main(args);
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2013-15 The Processing Foundation
|
||||
Copyright (c) 2010-13 Ben Fry and Casey Reas
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import processing.app.Language;
|
||||
import processing.app.ui.Editor;
|
||||
import processing.app.ui.EditorButton;
|
||||
import processing.app.ui.EditorToolbar;
|
||||
|
||||
|
||||
public class JavaToolbar extends EditorToolbar {
|
||||
JavaEditor jeditor;
|
||||
|
||||
// boolean debug; // true if this is the expanded debug feller
|
||||
EditorButton stepButton;
|
||||
EditorButton continueButton;
|
||||
|
||||
|
||||
// public JavaToolbar(Editor editor, boolean debug) {
|
||||
public JavaToolbar(Editor editor) {
|
||||
super(editor);
|
||||
// this.debug = debug;
|
||||
jeditor = (JavaEditor) editor;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<EditorButton> createButtons() {
|
||||
// jeditor not ready yet because this is called by super()
|
||||
final boolean debug = ((JavaEditor) editor).isDebuggerEnabled();
|
||||
// System.out.println("creating buttons in JavaToolbar, debug:" + debug);
|
||||
List<EditorButton> outgoing = new ArrayList<>();
|
||||
|
||||
final String runText = debug ?
|
||||
Language.text("toolbar.debug") : Language.text("toolbar.run");
|
||||
runButton = new EditorButton(this,
|
||||
"/lib/toolbar/run",
|
||||
runText,
|
||||
Language.text("toolbar.present")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
handleRun(e.getModifiers());
|
||||
}
|
||||
};
|
||||
outgoing.add(runButton);
|
||||
|
||||
if (debug) {
|
||||
stepButton = new EditorButton(this,
|
||||
"/lib/toolbar/step",
|
||||
Language.text("menu.debug.step"),
|
||||
Language.text("menu.debug.step_into"),
|
||||
Language.text("menu.debug.step_out")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
final int mask = ActionEvent.SHIFT_MASK | ActionEvent.ALT_MASK;
|
||||
jeditor.handleStep(e.getModifiers() & mask);
|
||||
}
|
||||
};
|
||||
outgoing.add(stepButton);
|
||||
|
||||
continueButton = new EditorButton(this,
|
||||
"/lib/toolbar/continue",
|
||||
Language.text("menu.debug.continue")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
jeditor.handleContinue();
|
||||
}
|
||||
};
|
||||
outgoing.add(continueButton);
|
||||
}
|
||||
|
||||
stopButton = new EditorButton(this,
|
||||
"/lib/toolbar/stop",
|
||||
Language.text("toolbar.stop")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
handleStop();
|
||||
}
|
||||
};
|
||||
outgoing.add(stopButton);
|
||||
|
||||
return outgoing;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addModeButtons(Box box, JLabel label) {
|
||||
EditorButton debugButton =
|
||||
new EditorButton(this, "/lib/toolbar/debug",
|
||||
Language.text("toolbar.debug")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
jeditor.toggleDebug();
|
||||
}
|
||||
};
|
||||
|
||||
if (((JavaEditor) editor).isDebuggerEnabled()) {
|
||||
debugButton.setSelected(true);
|
||||
}
|
||||
// debugButton.setRolloverLabel(label);
|
||||
box.add(debugButton);
|
||||
addGap(box);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleRun(int modifiers) {
|
||||
boolean shift = (modifiers & InputEvent.SHIFT_MASK) != 0;
|
||||
if (shift) {
|
||||
jeditor.handlePresent();
|
||||
} else {
|
||||
jeditor.handleRun();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleStop() {
|
||||
jeditor.handleStop();
|
||||
}
|
||||
|
||||
|
||||
public void activateContinue() {
|
||||
continueButton.setSelected(true);
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
protected void deactivateContinue() {
|
||||
continueButton.setSelected(false);
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
protected void activateStep() {
|
||||
stepButton.setSelected(true);
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
protected void deactivateStep() {
|
||||
stepButton.setSelected(false);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
+1028
File diff suppressed because it is too large
Load Diff
+60
@@ -0,0 +1,60 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.debug;
|
||||
|
||||
import com.sun.jdi.ArrayReference;
|
||||
import com.sun.jdi.ClassNotLoadedException;
|
||||
import com.sun.jdi.InvalidTypeException;
|
||||
import com.sun.jdi.Value;
|
||||
|
||||
import processing.app.Messages;
|
||||
|
||||
/**
|
||||
* Specialized {@link VariableNode} for representing single fields in an array.
|
||||
* Overrides {@link #setValue} to properly change the value of the encapsulated
|
||||
* array field.
|
||||
*/
|
||||
public class ArrayFieldNode extends VariableNode {
|
||||
protected ArrayReference array;
|
||||
protected int index;
|
||||
|
||||
|
||||
/**
|
||||
* Construct an {@link ArrayFieldNode}.
|
||||
*/
|
||||
public ArrayFieldNode(String name, String type, Value value, ArrayReference array, int index) {
|
||||
super(name, type, value);
|
||||
this.array = array;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setValue(Value value) {
|
||||
try {
|
||||
array.setValue(index, value);
|
||||
} catch (InvalidTypeException | ClassNotLoadedException ex) {
|
||||
Messages.loge(null, ex);
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.debug;
|
||||
|
||||
import com.sun.jdi.ReferenceType;
|
||||
|
||||
|
||||
/**
|
||||
* Listener to be notified when a class is loaded in the debugger. Used by
|
||||
* {@link LineBreakpoint}s to activate themselves as soon as the respective
|
||||
* class is loaded.
|
||||
*/
|
||||
public interface ClassLoadListener {
|
||||
|
||||
/**
|
||||
* Event handler called when a class is loaded.
|
||||
*/
|
||||
public void classLoaded(ReferenceType theClass);
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.debug;
|
||||
|
||||
import com.sun.jdi.ClassNotLoadedException;
|
||||
import com.sun.jdi.Field;
|
||||
import com.sun.jdi.InvalidTypeException;
|
||||
import com.sun.jdi.ObjectReference;
|
||||
import com.sun.jdi.Value;
|
||||
|
||||
import processing.app.Messages;
|
||||
|
||||
|
||||
/**
|
||||
* Specialized {@link VariableNode} for representing fields. Overrides
|
||||
* {@link #setValue} to properly change the value of the encapsulated field.
|
||||
*/
|
||||
public class FieldNode extends VariableNode {
|
||||
protected Field field;
|
||||
protected ObjectReference obj;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a {@link FieldNode}.
|
||||
* @param obj a reference to the object containing the field
|
||||
*/
|
||||
public FieldNode(String name, String type, Value value, Field field,
|
||||
ObjectReference obj) {
|
||||
super(name, type, value);
|
||||
this.field = field;
|
||||
this.obj = obj;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setValue(Value value) {
|
||||
try {
|
||||
obj.setValue(field, value);
|
||||
} catch (InvalidTypeException ite) {
|
||||
Messages.loge(null, ite);
|
||||
} catch (ClassNotLoadedException cnle) {
|
||||
Messages.loge(null, cnle);
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.debug;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import processing.app.Messages;
|
||||
import processing.mode.java.Debugger;
|
||||
|
||||
import com.sun.jdi.AbsentInformationException;
|
||||
import com.sun.jdi.Location;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import com.sun.jdi.VMDisconnectedException;
|
||||
import com.sun.jdi.request.BreakpointRequest;
|
||||
|
||||
|
||||
/**
|
||||
* Model/Controller of a line breakpoint. Can be set before or while debugging.
|
||||
* Adds a highlight using the debuggers view ({@link DebugEditor}).
|
||||
*/
|
||||
public class LineBreakpoint implements ClassLoadListener {
|
||||
protected Debugger dbg; // the debugger
|
||||
protected LineID line; // the line this breakpoint is set on
|
||||
protected BreakpointRequest bpr; // the request on the VM's event request manager
|
||||
protected String className;
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link LineBreakpoint}. If in a debug session, will try to
|
||||
* immediately set the breakpoint. If not in a debug session or the
|
||||
* corresponding class is not yet loaded the breakpoint will activate on
|
||||
* class load.
|
||||
*
|
||||
* @param line the line id to create the breakpoint on
|
||||
* @param dbg the {@link Debugger}
|
||||
*/
|
||||
public LineBreakpoint(LineID line, Debugger dbg) {
|
||||
this.line = line;
|
||||
line.startTracking(dbg.getEditor().getTab(line.fileName()).getDocument());
|
||||
this.dbg = dbg;
|
||||
this.className = className();
|
||||
set(); // activate the breakpoint (show highlight, attach if debugger is running)
|
||||
Messages.log("LBP Created " + toString() + " class: " + this.className);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link LineBreakpoint} on a line in the current tab.
|
||||
* @param lineIdx the line index of the current tab to create the breakpoint
|
||||
*/
|
||||
// TODO: remove and replace by {@link #LineBreakpoint(LineID line, Debugger dbg)}
|
||||
public LineBreakpoint(int lineIdx, Debugger dbg) {
|
||||
this(dbg.getEditor().getLineIDInCurrentTab(lineIdx), dbg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the line id this breakpoint is on.
|
||||
*/
|
||||
public LineID lineID() {
|
||||
return line;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if this breakpoint is on a certain line.
|
||||
*
|
||||
* @param testLine the line id to test
|
||||
* @return true if this breakpoint is on the given line
|
||||
*/
|
||||
public boolean isOnLine(LineID testLine) {
|
||||
return line.equals(testLine);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach this breakpoint to the VM. Creates and enables a
|
||||
* {@link BreakpointRequest}. VM needs to be paused.
|
||||
*
|
||||
* @param theClass class to attach to
|
||||
* @return true on success
|
||||
*/
|
||||
protected boolean attach(ReferenceType theClass) {
|
||||
|
||||
if (theClass == null || className == null ||
|
||||
!className.equals(parseTopLevelClassName(theClass.name()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log("trying to attach: " + line.fileName + ":" + line.lineIdx + " to " + theClass.name());
|
||||
|
||||
if (!dbg.isPaused()) {
|
||||
log("can't attach breakpoint, debugger not paused");
|
||||
return false;
|
||||
}
|
||||
|
||||
// find line in java space
|
||||
LineID javaLine = dbg.sketchToJavaLine(line);
|
||||
if (javaLine == null) {
|
||||
log("couldn't find line " + line + " in the java code");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
log("BPs of class: " + theClass + ", line " + (javaLine.lineIdx() + 1));
|
||||
List<Location> locations = theClass.locationsOfLine(javaLine.lineIdx() + 1);
|
||||
if (locations.isEmpty()) {
|
||||
log("no location found for line " + line + " -> " + javaLine);
|
||||
return false;
|
||||
}
|
||||
// use first found location
|
||||
bpr = dbg.vm().eventRequestManager().createBreakpointRequest(locations.get(0));
|
||||
bpr.enable();
|
||||
log("attached breakpoint to " + line + " -> " + javaLine);
|
||||
return true;
|
||||
} catch (AbsentInformationException ex) {
|
||||
Messages.loge(null, ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected boolean isAttached() {
|
||||
return bpr != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detach this breakpoint from the VM. Deletes the
|
||||
* {@link BreakpointRequest}.
|
||||
*/
|
||||
public void detach() {
|
||||
if (bpr != null) {
|
||||
try {
|
||||
dbg.vm().eventRequestManager().deleteEventRequest(bpr);
|
||||
} catch (VMDisconnectedException ignore) { }
|
||||
bpr = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set this breakpoint. Adds the line highlight. If Debugger is paused
|
||||
* also attaches the breakpoint by calling {@link #attach()}.
|
||||
*/
|
||||
protected void set() {
|
||||
dbg.addClassLoadListener(this); // class may not yet be loaded
|
||||
dbg.getEditor().addBreakpointedLine(line);
|
||||
if (className != null && dbg.isPaused()) { // debugging right now, try to attach
|
||||
for (ReferenceType rt : dbg.getClasses()) {
|
||||
// try to attach to all top level or nested classes
|
||||
if (attach(rt)) break;
|
||||
}
|
||||
}
|
||||
if (dbg.getEditor().isInCurrentTab(line)) {
|
||||
dbg.getEditor().getSketch().setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove this breakpoint. Clears the highlight and detaches
|
||||
* the breakpoint if the debugger is paused.
|
||||
*/
|
||||
public void remove() {
|
||||
dbg.removeClassLoadListener(this);
|
||||
//System.out.println("removing " + line.lineIdx());
|
||||
dbg.getEditor().removeBreakpointedLine(line.lineIdx());
|
||||
if (dbg.isPaused()) {
|
||||
// immediately remove the breakpoint
|
||||
detach();
|
||||
}
|
||||
line.stopTracking();
|
||||
if (dbg.getEditor().isInCurrentTab(line)) {
|
||||
dbg.getEditor().getSketch().setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return line.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the class this breakpoint belongs to. Needed for
|
||||
* fetching the right location to create a breakpoint request.
|
||||
* @return the class name
|
||||
*/
|
||||
protected String className() {
|
||||
if (line.fileName().endsWith(".pde")) {
|
||||
// standard tab
|
||||
return dbg.getEditor().getSketch().getName();
|
||||
}
|
||||
|
||||
if (line.fileName().endsWith(".java")) {
|
||||
// pure java tab
|
||||
return line.fileName().substring(0, line.fileName().lastIndexOf(".java"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Event handler called when a class is loaded in the debugger. Causes the
|
||||
* breakpoint to be attached, if its class was loaded.
|
||||
*
|
||||
* @param theClass the class that was just loaded.
|
||||
*/
|
||||
@Override
|
||||
public void classLoaded(ReferenceType theClass) {
|
||||
if (!isAttached()) {
|
||||
// try to attach
|
||||
attach(theClass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static public String parseTopLevelClassName(String name) {
|
||||
// Get rid of nested class name
|
||||
int dollar = name.indexOf('$');
|
||||
return (dollar == -1) ? name : name.substring(0, dollar);
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
private void log(String msg, Object... args) {
|
||||
if (args != null && args.length != 0) {
|
||||
Messages.logf(getClass().getName() + " " + msg, args);
|
||||
} else {
|
||||
Messages.log(getClass().getName() + " " + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.debug;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import processing.mode.java.JavaEditor;
|
||||
|
||||
|
||||
/**
|
||||
* Model/Controller for a highlighted source code line. Implements a custom
|
||||
* background color and a text based marker placed in the left-hand gutter area.
|
||||
*/
|
||||
public class LineHighlight {
|
||||
protected final JavaEditor editor; // the view, used for highlighting lines by setting a background color
|
||||
protected final LineID lineID; // the id of the line
|
||||
protected String marker; //
|
||||
protected int priority = 0;
|
||||
protected static final Set<LineHighlight> allHighlights = new HashSet<>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link LineHighlight}.
|
||||
*/
|
||||
public LineHighlight(LineID lineID, JavaEditor editor) {
|
||||
this.lineID = lineID;
|
||||
this.editor = editor;
|
||||
lineID.addListener(this);
|
||||
lineID.startTracking(editor.getTab(lineID.fileName()).getDocument()); // TODO: overwrite a previous doc?
|
||||
paint(); // already checks if on current tab
|
||||
allHighlights.add(this);
|
||||
}
|
||||
|
||||
|
||||
protected static boolean isHighestPriority(LineHighlight hl) {
|
||||
for (LineHighlight check : allHighlights) {
|
||||
if (check.getLineID().equals(hl.getLineID()) &&
|
||||
check.priority() > hl.priority()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void setPriority(int p) {
|
||||
this.priority = p;
|
||||
}
|
||||
|
||||
|
||||
public int priority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link LineHighlight} on the current tab.
|
||||
*
|
||||
* @param lineIdx the line index on the current tab to highlight
|
||||
* @param editor the {@link JavaEditor}
|
||||
*/
|
||||
// TODO: Remove and replace by {@link #LineHighlight(LineID lineID, JavaEditor editor)}
|
||||
public LineHighlight(int lineIdx, JavaEditor editor) {
|
||||
this(editor.getLineIDInCurrentTab(lineIdx), editor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a text based marker displayed in the left hand gutter area of this
|
||||
* highlighted line.
|
||||
*
|
||||
* @param marker the marker text
|
||||
*/
|
||||
public void setMarker(String marker) {
|
||||
this.marker = marker;
|
||||
paint();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the line id of this {@link LineHighlight}.
|
||||
*
|
||||
* @return the line id
|
||||
*/
|
||||
public LineID getLineID() {
|
||||
return lineID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test if this highlight is on a certain line.
|
||||
*
|
||||
* @param testLine the line to test
|
||||
* @return true if this highlight is on the given line
|
||||
*/
|
||||
public boolean isOnLine(LineID testLine) {
|
||||
return lineID.equals(testLine);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Event handler for line number changes (due to editing). Will remove the
|
||||
* highlight from the old line number and repaint it at the new location.
|
||||
*
|
||||
* @param line the line that has changed
|
||||
* @param oldLineIdx the old line index (0-based)
|
||||
* @param newLineIdx the new line index (0-based)
|
||||
*/
|
||||
public void lineChanged(LineID line, int oldLineIdx, int newLineIdx) {
|
||||
// clear old line
|
||||
if (editor.isInCurrentTab(new LineID(line.fileName(), oldLineIdx))) {
|
||||
editor.getJavaTextArea().clearGutterText(oldLineIdx);
|
||||
}
|
||||
|
||||
// paint new line
|
||||
// but only if it's on top -> fixes current line being hidden by breakpoint moving it down.
|
||||
// lineChanged events seem to come in inverse order of startTracking the LineID. (and bp is created first...)
|
||||
if (LineHighlight.isHighestPriority(this)) {
|
||||
paint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify this line highlight that it is no longer used. Call this for
|
||||
* cleanup before the {@link LineHighlight} is discarded.
|
||||
*/
|
||||
public void dispose() {
|
||||
lineID.removeListener(this);
|
||||
lineID.stopTracking();
|
||||
allHighlights.remove(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* (Re-)paint this line highlight.
|
||||
*/
|
||||
public void paint() {
|
||||
if (editor.isInCurrentTab(lineID)) {
|
||||
if (marker != null) {
|
||||
editor.getJavaTextArea().setGutterText(lineID.lineIdx(), marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear this line highlight.
|
||||
*/
|
||||
public void clear() {
|
||||
if (editor.isInCurrentTab(lineID)) {
|
||||
editor.getJavaTextArea().clearGutterText(lineID.lineIdx());
|
||||
}
|
||||
}
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.debug;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.Element;
|
||||
import javax.swing.text.Position;
|
||||
|
||||
import processing.app.Messages;
|
||||
|
||||
|
||||
/**
|
||||
* Describes an ID for a code line. Comprised of a file name and a (0-based)
|
||||
* line number. Can track changes to the line number due to text editing by
|
||||
* attaching a {@link Document}. Registered {@link LineListener}s are notified
|
||||
* of changes to the line number.
|
||||
*/
|
||||
public class LineID implements DocumentListener {
|
||||
protected String fileName; // the filename
|
||||
protected int lineIdx; // the line number, 0-based
|
||||
protected Document doc; // the Document to use for line number tracking
|
||||
protected Position pos; // the Position acquired during line number tracking
|
||||
protected Set<LineHighlight> listeners = new HashSet<LineHighlight>(); // listeners for line number changes
|
||||
|
||||
|
||||
public LineID(String fileName, int lineIdx) {
|
||||
this.fileName = fileName;
|
||||
this.lineIdx = lineIdx;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the file name of this line.
|
||||
*
|
||||
* @return the file name
|
||||
*/
|
||||
public String fileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the (0-based) line number of this line.
|
||||
*
|
||||
* @return the line index (i.e. line number, starting at 0)
|
||||
*/
|
||||
public synchronized int lineIdx() {
|
||||
return lineIdx;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test whether this {@link LineID} is equal to another object. Two
|
||||
* {@link LineID}'s are equal when both their fileName and lineNo are equal.
|
||||
*
|
||||
* @param obj the object to test for equality
|
||||
* @return {@code true} if equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final LineID other = (LineID) obj;
|
||||
if ((this.fileName == null) ? (other.fileName != null) : !this.fileName.equals(other.fileName)) {
|
||||
return false;
|
||||
}
|
||||
if (this.lineIdx != other.lineIdx) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Output a string representation in the form fileName:lineIdx+1. Note this
|
||||
* uses a 1-based line number as is customary for human-readable line
|
||||
* numbers.
|
||||
*
|
||||
* @return the string representation of this line ID
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return fileName + ":" + (lineIdx + 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach a {@link Document} to enable line number tracking when editing.
|
||||
* The position to track is before the first non-whitespace character on the
|
||||
* line. Edits happening before that position will cause the line number to
|
||||
* update accordingly. Multiple {@link #startTracking} calls will replace
|
||||
* the tracked document. Whoever wants a tracked line should track it and
|
||||
* add itself as listener if necessary.
|
||||
* ({@link LineHighlight}, {@link LineBreakpoint})
|
||||
*
|
||||
* @param doc the {@link Document} to use for line number tracking
|
||||
*/
|
||||
public synchronized void startTracking(Document doc) {
|
||||
//System.out.println("tracking: " + this);
|
||||
if (doc == null) {
|
||||
return; // null arg
|
||||
}
|
||||
if (doc == this.doc) {
|
||||
return; // already tracking that doc
|
||||
}
|
||||
try {
|
||||
Element line = doc.getDefaultRootElement().getElement(lineIdx);
|
||||
if (line == null) {
|
||||
return; // line doesn't exist
|
||||
}
|
||||
String lineText = doc.getText(line.getStartOffset(), line.getEndOffset() - line.getStartOffset());
|
||||
// set tracking position at (=before) first non-white space character on line,
|
||||
// or, if the line consists of entirely white spaces, just before the newline
|
||||
// character
|
||||
pos = doc.createPosition(line.getStartOffset() + nonWhiteSpaceOffset(lineText));
|
||||
this.doc = doc;
|
||||
doc.addDocumentListener(this);
|
||||
} catch (BadLocationException ex) {
|
||||
Messages.loge(null, ex);
|
||||
pos = null;
|
||||
this.doc = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify this {@link LineID} that it is no longer in use. Will stop
|
||||
* position tracking. Call this when this {@link LineID} is no longer
|
||||
* needed.
|
||||
*/
|
||||
public synchronized void stopTracking() {
|
||||
if (doc != null) {
|
||||
doc.removeDocumentListener(this);
|
||||
doc = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the tracked position. Will notify listeners if line number has
|
||||
* changed.
|
||||
*/
|
||||
protected synchronized void updatePosition() {
|
||||
if (doc != null && pos != null) {
|
||||
// track position
|
||||
int offset = pos.getOffset();
|
||||
int oldLineIdx = lineIdx;
|
||||
lineIdx = doc.getDefaultRootElement().getElementIndex(offset); // offset to lineNo
|
||||
if (lineIdx != oldLineIdx) {
|
||||
for (LineHighlight l : listeners) {
|
||||
if (l != null) {
|
||||
l.lineChanged(this, oldLineIdx, lineIdx);
|
||||
} else {
|
||||
listeners.remove(l); // remove null listener
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add listener to be notified when the line number changes.
|
||||
*
|
||||
* @param l the listener to add
|
||||
*/
|
||||
public void addListener(LineHighlight l) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a listener for line number changes.
|
||||
*
|
||||
* @param l the listener to remove
|
||||
*/
|
||||
public void removeListener(LineHighlight l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the offset of the first non-whitespace character in a string.
|
||||
* @param str the string to examine
|
||||
* @return offset of first non-whitespace character in str
|
||||
*/
|
||||
protected static int nonWhiteSpaceOffset(String str) {
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
if (!Character.isWhitespace(str.charAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// If we've reached here, that implies the line consists of purely white
|
||||
// space. So return at a position just after the whitespace (i.e.,
|
||||
// just before the newline).
|
||||
//
|
||||
// The " - 1" part resolves issue #3552
|
||||
return str.length() - 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when the {@link Document} registered using {@link #startTracking}
|
||||
* is edited. This happens when text is inserted or removed.
|
||||
*/
|
||||
protected void editEvent(DocumentEvent de) {
|
||||
//System.out.println("document edit @ " + de.getOffset());
|
||||
if (de.getOffset() <= pos.getOffset()) {
|
||||
updatePosition();
|
||||
//System.out.println("updating, new line no: " + lineNo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link DocumentListener} callback. Called when text is inserted.
|
||||
*/
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent de) {
|
||||
editEvent(de);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link DocumentListener} callback. Called when text is removed.
|
||||
*/
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent de) {
|
||||
editEvent(de);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link DocumentListener} callback. Called when attributes are changed.
|
||||
* Not used.
|
||||
*/
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent de) {
|
||||
// not needed.
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.debug;
|
||||
|
||||
import com.sun.jdi.ClassNotLoadedException;
|
||||
import com.sun.jdi.InvalidTypeException;
|
||||
import com.sun.jdi.LocalVariable;
|
||||
import com.sun.jdi.StackFrame;
|
||||
import com.sun.jdi.Value;
|
||||
|
||||
import processing.app.Messages;
|
||||
|
||||
|
||||
/**
|
||||
* Specialized {@link VariableNode} for representing local variables.
|
||||
* Overrides {@link #setValue} to properly change the value of the
|
||||
* encapsulated local variable.
|
||||
*/
|
||||
public class LocalVariableNode extends VariableNode {
|
||||
protected LocalVariable var;
|
||||
protected StackFrame frame;
|
||||
|
||||
|
||||
public LocalVariableNode(String name, String type, Value value, LocalVariable var, StackFrame frame) {
|
||||
super(name, type, value);
|
||||
this.var = var;
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setValue(Value value) {
|
||||
try {
|
||||
frame.setValue(var, value);
|
||||
} catch (InvalidTypeException | ClassNotLoadedException ex) {
|
||||
Messages.loge(null, ex);
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
version 2, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.debug;
|
||||
|
||||
import com.sun.jdi.ArrayReference;
|
||||
import com.sun.jdi.ObjectReference;
|
||||
import com.sun.jdi.StringReference;
|
||||
import com.sun.jdi.Value;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import javax.swing.tree.MutableTreeNode;
|
||||
import javax.swing.tree.TreeNode;
|
||||
|
||||
/**
|
||||
* Model for a variable in the variable inspector. Has a type and name and
|
||||
* optionally a value. Can have sub-variables (as is the case for objects,
|
||||
* and arrays).
|
||||
*/
|
||||
public class VariableNode implements MutableTreeNode {
|
||||
public static final int TYPE_UNKNOWN = -1;
|
||||
public static final int TYPE_OBJECT = 0;
|
||||
public static final int TYPE_ARRAY = 1;
|
||||
public static final int TYPE_INTEGER = 2;
|
||||
public static final int TYPE_FLOAT = 3;
|
||||
public static final int TYPE_BOOLEAN = 4;
|
||||
public static final int TYPE_CHAR = 5;
|
||||
public static final int TYPE_STRING = 6;
|
||||
public static final int TYPE_LONG = 7;
|
||||
public static final int TYPE_DOUBLE = 8;
|
||||
public static final int TYPE_BYTE = 9;
|
||||
public static final int TYPE_SHORT = 10;
|
||||
public static final int TYPE_VOID = 11;
|
||||
|
||||
protected String type;
|
||||
protected String name;
|
||||
protected Value value;
|
||||
protected List<MutableTreeNode> children = new ArrayList<>();
|
||||
protected MutableTreeNode parent;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a {@link VariableNode}.
|
||||
* @param name the name
|
||||
* @param type the type
|
||||
* @param value the value
|
||||
*/
|
||||
public VariableNode(String name, String type, Value value) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
public void setValue(Value value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
public Value getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a String representation of this variable nodes value.
|
||||
*
|
||||
* @return a String representing the value.
|
||||
*/
|
||||
public String getStringValue() {
|
||||
String str;
|
||||
if (value != null) {
|
||||
if (getType() == TYPE_OBJECT) {
|
||||
str = "instance of " + type;
|
||||
} else if (getType() == TYPE_ARRAY) {
|
||||
//instance of int[5] (id=998) --> instance of int[5]
|
||||
str = value.toString().substring(0, value.toString().lastIndexOf(" "));
|
||||
} else if (getType() == TYPE_STRING) {
|
||||
str = ((StringReference) value).value(); // use original string value (without quotes)
|
||||
} else {
|
||||
str = value.toString();
|
||||
}
|
||||
} else {
|
||||
str = "null";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
public String getTypeName() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
public int getType() {
|
||||
if (type == null) {
|
||||
return TYPE_UNKNOWN;
|
||||
}
|
||||
if (type.endsWith("[]")) {
|
||||
return TYPE_ARRAY;
|
||||
}
|
||||
if (type.equals("int")) {
|
||||
return TYPE_INTEGER;
|
||||
}
|
||||
if (type.equals("long")) {
|
||||
return TYPE_LONG;
|
||||
}
|
||||
if (type.equals("byte")) {
|
||||
return TYPE_BYTE;
|
||||
}
|
||||
if (type.equals("short")) {
|
||||
return TYPE_SHORT;
|
||||
}
|
||||
if (type.equals("float")) {
|
||||
return TYPE_FLOAT;
|
||||
}
|
||||
if (type.equals("double")) {
|
||||
return TYPE_DOUBLE;
|
||||
}
|
||||
if (type.equals("char")) {
|
||||
return TYPE_CHAR;
|
||||
}
|
||||
if (type.equals("java.lang.String")) {
|
||||
return TYPE_STRING;
|
||||
}
|
||||
if (type.equals("boolean")) {
|
||||
return TYPE_BOOLEAN;
|
||||
}
|
||||
if (type.equals("void")) {
|
||||
return TYPE_VOID; //TODO: check if this is correct
|
||||
}
|
||||
return TYPE_OBJECT;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a {@link VariableNode} as child.
|
||||
*
|
||||
* @param c the {@link VariableNode} to add.
|
||||
*/
|
||||
public void addChild(VariableNode c) {
|
||||
children.add(c);
|
||||
c.setParent(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add multiple {@link VariableNode}s as children.
|
||||
*
|
||||
* @param children the list of {@link VariableNode}s to add.
|
||||
*/
|
||||
public void addChildren(List<VariableNode> children) {
|
||||
for (VariableNode child : children) {
|
||||
addChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TreeNode getChildAt(int i) {
|
||||
return children.get(i);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getChildCount() {
|
||||
return children.size();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TreeNode getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getIndex(TreeNode tn) {
|
||||
return children.indexOf(tn);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean getAllowsChildren() {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle strings
|
||||
if (getType() == TYPE_STRING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle arrays
|
||||
if (getType() == TYPE_ARRAY) {
|
||||
ArrayReference array = (ArrayReference) value;
|
||||
return array.length() > 0;
|
||||
}
|
||||
// handle objects
|
||||
if (getType() == TYPE_OBJECT) { // this also rules out null
|
||||
// check if this object has any fields
|
||||
ObjectReference obj = (ObjectReference) value;
|
||||
return !obj.referenceType().visibleFields().isEmpty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This controls the default icon and disclosure triangle.
|
||||
*
|
||||
* @return true, will show "folder" icon and disclosure triangle.
|
||||
*/
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
//return children.size() == 0;
|
||||
return !getAllowsChildren();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Enumeration<MutableTreeNode> children() {
|
||||
return Collections.enumeration(children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a String representation of this {@link VariableNode}.
|
||||
*
|
||||
* @return the name of the variable (for sorting to work).
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName(); // for sorting
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a String description of this {@link VariableNode}. Contains the type,
|
||||
* name and value.
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
String str = "";
|
||||
if (type != null) {
|
||||
str += type + " ";
|
||||
}
|
||||
str += name;
|
||||
str += " = " + getStringValue();
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void insert(MutableTreeNode mtn, int i) {
|
||||
children.add(i, this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void remove(int i) {
|
||||
MutableTreeNode mtn = children.remove(i);
|
||||
if (mtn != null) {
|
||||
mtn.setParent(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void remove(MutableTreeNode mtn) {
|
||||
children.remove(mtn);
|
||||
mtn.setParent(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove all children from this {@link VariableNode}.
|
||||
*/
|
||||
public void removeAllChildren() {
|
||||
for (MutableTreeNode mtn : children) {
|
||||
mtn.setParent(null);
|
||||
}
|
||||
children.clear();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setUserObject(Object o) {
|
||||
if (o instanceof Value) {
|
||||
value = (Value) o;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeFromParent() {
|
||||
parent.remove(this);
|
||||
this.parent = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setParent(MutableTreeNode mtn) {
|
||||
parent = mtn;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test for equality. To be equal, two {@link VariableNode}s need to have
|
||||
* equal type, name and value.
|
||||
*
|
||||
* @param obj the object to test for equality with this {@link VariableNode}
|
||||
* @return true if the given object is equal to this {@link VariableNode}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final VariableNode other = (VariableNode) obj;
|
||||
if ((this.type == null) ? (other.type != null) : !this.type.equals(other.type)) {
|
||||
//System.out.println("type not equal");
|
||||
return false;
|
||||
}
|
||||
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
|
||||
//System.out.println("name not equal");
|
||||
return false;
|
||||
}
|
||||
if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
|
||||
//System.out.println("value not equal");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a hash code based on type, name and value.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 97 * hash + (this.type != null ? this.type.hashCode() : 0);
|
||||
hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0);
|
||||
hash = 97 * hash + (this.value != null ? this.value.hashCode() : 0);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
|
||||
import org.eclipse.jdt.core.dom.IBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.Name;
|
||||
import org.eclipse.jdt.core.dom.NodeFinder;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.Type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import processing.app.Messages;
|
||||
|
||||
|
||||
public class ASTUtils {
|
||||
|
||||
public static ASTNode getASTNodeAt(ASTNode root, int startJavaOffset, int stopJavaOffset) {
|
||||
Messages.log("* getASTNodeAt");
|
||||
|
||||
int length = stopJavaOffset - startJavaOffset;
|
||||
|
||||
NodeFinder f = new NodeFinder(root, startJavaOffset, length);
|
||||
ASTNode node = f.getCoveredNode();
|
||||
if (node == null) {
|
||||
node = f.getCoveringNode();
|
||||
}
|
||||
if (node == null) {
|
||||
Messages.log("no node found");
|
||||
} else {
|
||||
Messages.log("found " + node.getClass().getSimpleName());
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
public static SimpleName getSimpleNameAt(ASTNode root, int startJavaOffset, int stopJavaOffset) {
|
||||
Messages.log("* getSimpleNameAt");
|
||||
|
||||
// Find node at offset
|
||||
ASTNode node = getASTNodeAt(root, startJavaOffset, stopJavaOffset);
|
||||
|
||||
SimpleName result = null;
|
||||
|
||||
if (node == null) {
|
||||
result = null;
|
||||
} else if (node.getNodeType() == ASTNode.SIMPLE_NAME) {
|
||||
result = (SimpleName) node;
|
||||
} else {
|
||||
// Return SimpleName with highest coverage
|
||||
List<SimpleName> simpleNames = getSimpleNameChildren(node);
|
||||
if (!simpleNames.isEmpty()) {
|
||||
// Compute coverage <selection x node>
|
||||
int[] coverages = simpleNames.stream()
|
||||
.mapToInt(name -> {
|
||||
int start = name.getStartPosition();
|
||||
int stop = start + name.getLength();
|
||||
return Math.min(stop, stopJavaOffset) -
|
||||
Math.max(startJavaOffset, start);
|
||||
})
|
||||
.toArray();
|
||||
// Select node with highest coverage
|
||||
int maxIndex = IntStream.range(0, simpleNames.size())
|
||||
.filter(i -> coverages[i] >= 0)
|
||||
.reduce((i, j) -> coverages[i] > coverages[j] ? i : j)
|
||||
.orElse(-1);
|
||||
if (maxIndex == -1) return null;
|
||||
result = simpleNames.get(maxIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
Messages.log("no simple name found");
|
||||
} else {
|
||||
Messages.log("found " + node.toString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static List<SimpleName> getSimpleNameChildren(ASTNode node) {
|
||||
List<SimpleName> simpleNames = new ArrayList<>();
|
||||
node.accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(SimpleName simpleName) {
|
||||
simpleNames.add(simpleName);
|
||||
return super.visit(simpleName);
|
||||
}
|
||||
});
|
||||
return simpleNames;
|
||||
}
|
||||
|
||||
|
||||
public static IBinding resolveBinding(SimpleName node) {
|
||||
IBinding binding = node.resolveBinding();
|
||||
if (binding == null) return null;
|
||||
|
||||
// Fix constructor call/declaration being resolved as type
|
||||
if (binding.getKind() == IBinding.TYPE) {
|
||||
ASTNode context = node;
|
||||
|
||||
// Go up until we find non Name or Type node
|
||||
// stop if context is type argument (parent is also Name/Type, but unrelated)
|
||||
while (isNameOrType(context) &&
|
||||
!context.getLocationInParent().getId().equals("typeArguments")) {
|
||||
context = context.getParent();
|
||||
}
|
||||
|
||||
switch (context.getNodeType()) {
|
||||
case ASTNode.METHOD_DECLARATION:
|
||||
MethodDeclaration decl = (MethodDeclaration) context;
|
||||
if (decl.isConstructor()) {
|
||||
binding = decl.resolveBinding();
|
||||
}
|
||||
break;
|
||||
case ASTNode.CLASS_INSTANCE_CREATION:
|
||||
ClassInstanceCreation cic = (ClassInstanceCreation) context;
|
||||
binding = cic.resolveConstructorBinding();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (binding == null) return null;
|
||||
|
||||
// Normalize parametrized and raw bindings into generic bindings
|
||||
switch (binding.getKind()) {
|
||||
case IBinding.TYPE:
|
||||
ITypeBinding type = (ITypeBinding) binding;
|
||||
if (type.isParameterizedType() || type.isRawType()) {
|
||||
binding = type.getErasure();
|
||||
}
|
||||
break;
|
||||
case IBinding.METHOD:
|
||||
IMethodBinding method = (IMethodBinding) binding;
|
||||
ITypeBinding declaringClass = method.getDeclaringClass();
|
||||
if (declaringClass.isParameterizedType() ||
|
||||
declaringClass.isRawType()) {
|
||||
IMethodBinding[] methods = declaringClass.getErasure().getDeclaredMethods();
|
||||
IMethodBinding generic = Arrays.stream(methods)
|
||||
.filter(method::overrides)
|
||||
.findAny().orElse(null);
|
||||
if (generic != null) method = generic;
|
||||
}
|
||||
if (method.isParameterizedMethod() || method.isRawMethod()) {
|
||||
method = method.getMethodDeclaration();
|
||||
}
|
||||
binding = method;
|
||||
break;
|
||||
}
|
||||
|
||||
return binding;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isNameOrType(ASTNode node) {
|
||||
return node instanceof Name || node instanceof Type;
|
||||
}
|
||||
|
||||
|
||||
protected static List<SimpleName> findAllOccurrences(ASTNode root, String bindingKey) {
|
||||
List<SimpleName> occurences = new ArrayList<>();
|
||||
root.getRoot().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(SimpleName name) {
|
||||
IBinding binding = resolveBinding(name);
|
||||
if (binding != null && bindingKey.equals(binding.getKey())) {
|
||||
occurences.add(name);
|
||||
}
|
||||
return super.visit(name);
|
||||
}
|
||||
});
|
||||
|
||||
return occurences;
|
||||
}
|
||||
|
||||
}
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-18 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.FieldDeclaration;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
|
||||
|
||||
|
||||
// TODO when building the label in some variants in this file,
|
||||
// getReturnType2() is used instead of getReturnType().
|
||||
// need to check whether that's identical in how it performs,
|
||||
// and if so, use makeLabel() and makeCompletion() more [fry 180326]
|
||||
// https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FMethodDeclaration.html
|
||||
|
||||
public class CompletionCandidate implements Comparable<CompletionCandidate> {
|
||||
private final String elementName;
|
||||
private final String label; // the toString value
|
||||
private final String completion;
|
||||
private final Object wrappedObject;
|
||||
private final int type;
|
||||
|
||||
static final int PREDEF_CLASS = 0;
|
||||
static final int PREDEF_FIELD = 1;
|
||||
static final int PREDEF_METHOD = 2;
|
||||
static final int LOCAL_CLASS = 3;
|
||||
static final int LOCAL_METHOD = 4;
|
||||
static final int LOCAL_FIELD = 5;
|
||||
static final int LOCAL_VAR = 6;
|
||||
|
||||
|
||||
CompletionCandidate(Method method) {
|
||||
// return value ignored? [fry 180326]
|
||||
method.getDeclaringClass().getName();
|
||||
elementName = method.getName();
|
||||
label = makeLabel(method);
|
||||
completion = makeCompletion(method);
|
||||
type = PREDEF_METHOD;
|
||||
wrappedObject = method;
|
||||
}
|
||||
|
||||
|
||||
CompletionCandidate(SingleVariableDeclaration svd) {
|
||||
completion = svd.getName().toString();
|
||||
elementName = svd.getName().toString();
|
||||
type = (svd.getParent() instanceof FieldDeclaration) ?
|
||||
LOCAL_FIELD : LOCAL_VAR;
|
||||
label = svd.getName() + " : " + svd.getType();
|
||||
wrappedObject = svd;
|
||||
}
|
||||
|
||||
|
||||
CompletionCandidate(VariableDeclarationFragment vdf) {
|
||||
completion = vdf.getName().toString();
|
||||
elementName = vdf.getName().toString();
|
||||
type = (vdf.getParent() instanceof FieldDeclaration) ?
|
||||
LOCAL_FIELD : LOCAL_VAR;
|
||||
label = vdf.getName() + " : " + CompletionGenerator.extracTypeInfo2(vdf);
|
||||
wrappedObject = vdf;
|
||||
}
|
||||
|
||||
|
||||
CompletionCandidate(MethodDeclaration method) {
|
||||
elementName = method.getName().toString();
|
||||
type = LOCAL_METHOD;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ASTNode> params = (List<ASTNode>)
|
||||
method.getStructuralProperty(MethodDeclaration.PARAMETERS_PROPERTY);
|
||||
|
||||
{ // label
|
||||
StringBuilder labelBuilder = new StringBuilder(elementName);
|
||||
labelBuilder.append('(');
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
labelBuilder.append(params.get(i).toString());
|
||||
if (i < params.size() - 1) {
|
||||
labelBuilder.append(',');
|
||||
}
|
||||
}
|
||||
labelBuilder.append(')');
|
||||
if (method.getReturnType2() != null) {
|
||||
labelBuilder.append(" : ");
|
||||
labelBuilder.append(method.getReturnType2());
|
||||
}
|
||||
label = labelBuilder.toString();
|
||||
}
|
||||
|
||||
{ // completion
|
||||
StringBuilder compBuilder = new StringBuilder(elementName);
|
||||
compBuilder.append('(');
|
||||
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
if (i < params.size() - 1) {
|
||||
compBuilder.append(',');
|
||||
}
|
||||
}
|
||||
if (params.size() == 1) {
|
||||
compBuilder.append(' ');
|
||||
}
|
||||
compBuilder.append(')');
|
||||
completion = compBuilder.toString();
|
||||
}
|
||||
|
||||
wrappedObject = method;
|
||||
}
|
||||
|
||||
|
||||
CompletionCandidate(TypeDeclaration td) {
|
||||
type = LOCAL_CLASS;
|
||||
elementName = td.getName().toString();
|
||||
label = elementName;
|
||||
completion = elementName;
|
||||
wrappedObject = td;
|
||||
}
|
||||
|
||||
|
||||
CompletionCandidate(Field f) {
|
||||
f.getDeclaringClass().getName();
|
||||
elementName = f.getName();
|
||||
type = PREDEF_FIELD;
|
||||
label = "<html>" +
|
||||
f.getName() + " : " +
|
||||
f.getType().getSimpleName() + " - " +
|
||||
"<font color=#777777>" +
|
||||
f.getDeclaringClass().getSimpleName() +
|
||||
"</font></html>";
|
||||
completion = elementName;
|
||||
wrappedObject = f;
|
||||
}
|
||||
|
||||
|
||||
CompletionCandidate(String elementName, String label,
|
||||
String completion, int type) {
|
||||
this(elementName, label, completion, type, null);
|
||||
}
|
||||
|
||||
|
||||
private CompletionCandidate(String elementName, String label,
|
||||
String completion, int type,
|
||||
Object wrappedObject) {
|
||||
this.elementName = elementName;
|
||||
this.label = label;
|
||||
this.completion = completion;
|
||||
this.type = type;
|
||||
this.wrappedObject = wrappedObject;
|
||||
}
|
||||
|
||||
|
||||
Object getWrappedObject() {
|
||||
return wrappedObject;
|
||||
}
|
||||
|
||||
|
||||
public String getElementName() {
|
||||
return elementName;
|
||||
}
|
||||
|
||||
|
||||
public String getCompletionString() {
|
||||
return completion;
|
||||
}
|
||||
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
// TODO this is gross [fry 180326]
|
||||
/*
|
||||
private String getNoHtmlLabel(){
|
||||
if (!label.contains("<html>")) {
|
||||
return label;
|
||||
|
||||
} else {
|
||||
StringBuilder ans = new StringBuilder(label);
|
||||
while (ans.indexOf("<") > -1) {
|
||||
int a = ans.indexOf("<"), b = ans.indexOf(">");
|
||||
if (a > b) break;
|
||||
ans.replace(a, b+1, "");
|
||||
}
|
||||
return ans.toString();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
boolean startsWith(String newWord) {
|
||||
// System.out.println("checking " + newWord);
|
||||
// return getNoHtmlLabel().toLowerCase().startsWith(newWord);
|
||||
// this seems to be elementName in all cases [fry 180326]
|
||||
return elementName.startsWith(newWord);
|
||||
}
|
||||
|
||||
|
||||
CompletionCandidate withLabelAndCompString(String withLabel,
|
||||
String withCompletion) {
|
||||
return new CompletionCandidate(elementName,
|
||||
withLabel, withCompletion,
|
||||
type, wrappedObject);
|
||||
}
|
||||
|
||||
|
||||
CompletionCandidate withRegeneratedCompString() {
|
||||
if (wrappedObject instanceof MethodDeclaration) {
|
||||
MethodDeclaration method = (MethodDeclaration)wrappedObject;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ASTNode> params = (List<ASTNode>)
|
||||
method.getStructuralProperty(MethodDeclaration.PARAMETERS_PROPERTY);
|
||||
|
||||
// build the html label
|
||||
StringBuilder labelBuilder = new StringBuilder(elementName);
|
||||
labelBuilder.append('(');
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
labelBuilder.append(params.get(i));
|
||||
if (i < params.size() - 1) {
|
||||
labelBuilder.append(',');
|
||||
}
|
||||
}
|
||||
labelBuilder.append(')');
|
||||
if (method.getReturnType2() != null) {
|
||||
labelBuilder.append(" : ");
|
||||
labelBuilder.append(method.getReturnType2());
|
||||
}
|
||||
|
||||
// build the completion str
|
||||
StringBuilder compBuilder = new StringBuilder();
|
||||
compBuilder.append(method.getName());
|
||||
compBuilder.append('(');
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
if (i < params.size() - 1) {
|
||||
compBuilder.append(',');
|
||||
}
|
||||
}
|
||||
if (params.size() == 1) {
|
||||
compBuilder.append(' ');
|
||||
}
|
||||
compBuilder.append(')');
|
||||
|
||||
return withLabelAndCompString(labelBuilder.toString(), compBuilder.toString());
|
||||
|
||||
} else if (wrappedObject instanceof Method) {
|
||||
Method method = (Method) wrappedObject;
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
|
||||
// build html label
|
||||
StringBuilder labelBuilder = new StringBuilder();
|
||||
labelBuilder.append("<html>");
|
||||
labelBuilder.append(method.getName());
|
||||
labelBuilder.append('(');
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
labelBuilder.append(types[i].getSimpleName());
|
||||
if (i < types.length - 1) {
|
||||
labelBuilder.append(',');
|
||||
}
|
||||
}
|
||||
labelBuilder.append(')');
|
||||
if (method.getReturnType() != null) {
|
||||
labelBuilder.append(" : " + method.getReturnType().getSimpleName());
|
||||
}
|
||||
|
||||
labelBuilder.append(" - <font color=#777777>");
|
||||
labelBuilder.append(method.getDeclaringClass().getSimpleName());
|
||||
labelBuilder.append("</font>");
|
||||
labelBuilder.append("</html>");
|
||||
|
||||
// make completion string
|
||||
StringBuilder compBuilder = new StringBuilder(method.getName());
|
||||
compBuilder.append('(');
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (i < types.length - 1) {
|
||||
compBuilder.append(',');
|
||||
}
|
||||
}
|
||||
if (types.length == 1) {
|
||||
compBuilder.append(' ');
|
||||
}
|
||||
compBuilder.append(')');
|
||||
|
||||
return withLabelAndCompString(labelBuilder.toString(), compBuilder.toString());
|
||||
}
|
||||
|
||||
// fall-through silently does nothing? [fry 180326]
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
static private String makeLabel(Method method) {
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
|
||||
StringBuilder labelBuilder = new StringBuilder();
|
||||
labelBuilder.append("<html>");
|
||||
labelBuilder.append(method.getName());
|
||||
labelBuilder.append('(');
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
labelBuilder.append(types[i].getSimpleName());
|
||||
if (i < types.length - 1) {
|
||||
labelBuilder.append(',');
|
||||
}
|
||||
}
|
||||
labelBuilder.append(")");
|
||||
if (method.getReturnType() != null) {
|
||||
labelBuilder.append(" : ");
|
||||
labelBuilder.append(method.getReturnType().getSimpleName());
|
||||
}
|
||||
labelBuilder.append(" - <font color=#777777>");
|
||||
labelBuilder.append(method.getDeclaringClass().getSimpleName());
|
||||
labelBuilder.append("</font>");
|
||||
labelBuilder.append("</html>");
|
||||
|
||||
return labelBuilder.toString();
|
||||
}
|
||||
|
||||
|
||||
static private String makeCompletion(Method method) {
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
|
||||
StringBuilder compBuilder = new StringBuilder();
|
||||
compBuilder.append(method.getName());
|
||||
compBuilder.append('(');
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
if (i < types.length - 1) {
|
||||
compBuilder.append(','); // wtf? [fry 180326]
|
||||
}
|
||||
}
|
||||
if (types.length == 1) {
|
||||
compBuilder.append(' ');
|
||||
}
|
||||
compBuilder.append(')');
|
||||
return compBuilder.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareTo(CompletionCandidate cc) {
|
||||
if (type != cc.getType()) {
|
||||
return cc.getType() - type;
|
||||
}
|
||||
return elementName.compareTo(cc.getElementName());
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
+2086
File diff suppressed because it is too large
Load Diff
+595
@@ -0,0 +1,595 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Messages;
|
||||
import processing.app.Mode;
|
||||
import processing.app.syntax.JEditTextArea;
|
||||
import processing.app.ui.Toolkit;
|
||||
import processing.mode.java.JavaEditor;
|
||||
|
||||
|
||||
public class CompletionPanel {
|
||||
/**
|
||||
* The completion list generated by ASTGenerator
|
||||
*/
|
||||
private JList<CompletionCandidate> completionList;
|
||||
|
||||
/**
|
||||
* The popup menu in which the suggestion list is shown
|
||||
*/
|
||||
private JPopupMenu popupMenu;
|
||||
|
||||
/**
|
||||
* Partial word which triggered the code completion and which needs to be completed
|
||||
*/
|
||||
private String subWord;
|
||||
|
||||
/**
|
||||
* Position where the completion has to be inserted
|
||||
*/
|
||||
private int insertionPosition;
|
||||
|
||||
private JavaTextArea textarea;
|
||||
|
||||
/**
|
||||
* Scroll pane in which the completion list is displayed
|
||||
*/
|
||||
private JScrollPane scrollPane;
|
||||
|
||||
protected JavaEditor editor;
|
||||
|
||||
static protected final int MOUSE_COMPLETION = 10, KEYBOARD_COMPLETION = 20;
|
||||
|
||||
private boolean horizontalScrollBarVisible = false;
|
||||
|
||||
static public ImageIcon classIcon;
|
||||
static public ImageIcon fieldIcon;
|
||||
static public ImageIcon methodIcon;
|
||||
static public ImageIcon localVarIcon;
|
||||
|
||||
static Color selectionBgColor;
|
||||
static Color textColor;
|
||||
|
||||
|
||||
/**
|
||||
* Triggers the completion popup
|
||||
* @param textarea
|
||||
* @param position - insertion position(caret pos)
|
||||
* @param subWord - Partial word which triggered the code completion and which needs to be completed
|
||||
* @param items - completion candidates
|
||||
* @param location - Point location where popup list is to be displayed
|
||||
* @param dedit
|
||||
*/
|
||||
public CompletionPanel(final JEditTextArea textarea,
|
||||
int position, String subWord,
|
||||
DefaultListModel<CompletionCandidate> items,
|
||||
final Point location, JavaEditor editor) {
|
||||
this.textarea = (JavaTextArea) textarea;
|
||||
this.editor = editor;
|
||||
this.insertionPosition = position;
|
||||
if (subWord.indexOf('.') != -1 && subWord.indexOf('.') != subWord.length()-1) {
|
||||
this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
|
||||
} else {
|
||||
this.subWord = subWord;
|
||||
}
|
||||
|
||||
if (classIcon == null) {
|
||||
Mode mode = editor.getMode();
|
||||
|
||||
File dir = new File(mode.getFolder(), "theme/completion");
|
||||
classIcon = Toolkit.getIconX(dir, "class_obj");
|
||||
methodIcon = Toolkit.getIconX(dir, "methpub_obj");
|
||||
fieldIcon = Toolkit.getIconX(dir, "field_protected_obj");
|
||||
localVarIcon = Toolkit.getIconX(dir, "field_default_obj");
|
||||
|
||||
//selectionBgColor = mode.getColor(""); // no theme.txt for Java Mode
|
||||
selectionBgColor = new Color(0xffF0F0F0);
|
||||
textColor = new Color(0xff222222);
|
||||
}
|
||||
|
||||
popupMenu = new JPopupMenu();
|
||||
popupMenu.removeAll();
|
||||
popupMenu.setOpaque(false);
|
||||
popupMenu.setBorder(null);
|
||||
|
||||
scrollPane = new JScrollPane();
|
||||
// styleScrollPane();
|
||||
//scrollPane.setViewportView(completionList = createSuggestionList(position, items));
|
||||
completionList = new JList<CompletionCandidate>(items) {
|
||||
{
|
||||
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
setSelectedIndex(0);
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
insertSelection(MOUSE_COMPLETION);
|
||||
setInvisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
setCellRenderer(new CustomListRenderer());
|
||||
setFocusable(false);
|
||||
setFont(Toolkit.getSansFont(12, Font.PLAIN));
|
||||
}
|
||||
};
|
||||
scrollPane.setViewportView(completionList);
|
||||
// remove an ugly multi-line border around it
|
||||
scrollPane.setBorder(null);
|
||||
|
||||
popupMenu.add(scrollPane, BorderLayout.CENTER);
|
||||
popupMenu.setPopupSize(calcWidth(), calcHeight(items.getSize())); //TODO: Eradicate this evil
|
||||
popupMenu.setFocusable(false);
|
||||
// TODO: Update JavaDoc to completionList.getSelectedValue()
|
||||
popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0) + location.y);
|
||||
//log("Suggestion shown: " + System.currentTimeMillis());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
private void styleScrollPane() {
|
||||
String laf = UIManager.getLookAndFeel().getID();
|
||||
if (!laf.equals("Nimbus") && !laf.equals("Windows")) return;
|
||||
|
||||
String thumbColor = null;
|
||||
if (laf.equals("Nimbus")) {
|
||||
UIDefaults defaults = new UIDefaults();
|
||||
defaults.put("PopupMenu.contentMargins", new InsetsUIResource(0, 0, 0, 0));
|
||||
defaults.put("ScrollPane[Enabled].borderPainter", new Painter<JComponent>() {
|
||||
public void paint(Graphics2D g, JComponent t, int w, int h) {}
|
||||
});
|
||||
popupMenu.putClientProperty("Nimbus.Overrides", defaults);
|
||||
scrollPane.putClientProperty("Nimbus.Overrides", defaults);
|
||||
thumbColor = "nimbusBlueGrey";
|
||||
} else if (laf.equals("Windows")) {
|
||||
thumbColor = "ScrollBar.thumbShadow";
|
||||
}
|
||||
|
||||
scrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(Integer.MAX_VALUE, 8));
|
||||
scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(8, Integer.MAX_VALUE));
|
||||
scrollPane.getHorizontalScrollBar().setUI(new CompletionScrollBarUI(thumbColor));
|
||||
scrollPane.getVerticalScrollBar().setUI(new CompletionScrollBarUI(thumbColor));
|
||||
}
|
||||
|
||||
|
||||
private static class CompletionScrollBarUI extends BasicScrollBarUI {
|
||||
private String thumbColorName;
|
||||
|
||||
protected CompletionScrollBarUI(String thumbColorName) {
|
||||
this.thumbColorName = thumbColorName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintThumb(Graphics g, JComponent c, Rectangle trackBounds) {
|
||||
g.setColor((Color) UIManager.get(thumbColorName));
|
||||
g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JButton createDecreaseButton(int orientation) {
|
||||
return createZeroButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JButton createIncreaseButton(int orientation) {
|
||||
return createZeroButton();
|
||||
}
|
||||
|
||||
static private JButton createZeroButton() {
|
||||
JButton jbutton = new JButton();
|
||||
jbutton.setPreferredSize(new Dimension(0, 0));
|
||||
jbutton.setMinimumSize(new Dimension(0, 0));
|
||||
jbutton.setMaximumSize(new Dimension(0, 0));
|
||||
return jbutton;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
public boolean isVisible() {
|
||||
return popupMenu.isVisible();
|
||||
}
|
||||
|
||||
|
||||
public void setInvisible() {
|
||||
popupMenu.setVisible(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dynamic height of completion panel depending on item count
|
||||
*/
|
||||
private int calcHeight(int itemCount) {
|
||||
int maxHeight = 250;
|
||||
FontMetrics fm = textarea.getGraphics().getFontMetrics();
|
||||
float itemHeight = Math.max((fm.getHeight() + (fm.getDescent()) * 0.5f),
|
||||
classIcon.getIconHeight() * 1.2f);
|
||||
|
||||
if (horizontalScrollBarVisible) {
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
if (itemCount < 4) {
|
||||
itemHeight *= 1.3f; //Sorry, but it works.
|
||||
}
|
||||
|
||||
float h = itemHeight * (itemCount);
|
||||
|
||||
if (itemCount >= 4) {
|
||||
h += itemHeight * 0.3; // a bit of offset
|
||||
}
|
||||
|
||||
return Math.min(maxHeight, (int) h); // popup menu height
|
||||
}
|
||||
|
||||
|
||||
private int calcWidth() {
|
||||
int maxWidth = 300;
|
||||
float min = 0;
|
||||
FontMetrics fm = textarea.getGraphics().getFontMetrics();
|
||||
for (int i = 0; i < completionList.getModel().getSize(); i++) {
|
||||
float h = fm.stringWidth(completionList.getModel().getElementAt(i).getLabel());
|
||||
min = Math.max(min, h);
|
||||
}
|
||||
int w = Math.min((int) min, maxWidth);
|
||||
horizontalScrollBarVisible = (w == maxWidth);
|
||||
w += classIcon.getIconWidth(); // add icon width too!
|
||||
w += fm.stringWidth(" "); // a bit of offset
|
||||
//log("popup width " + w);
|
||||
return w; // popup menu width
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Created the popup list to be displayed
|
||||
* @param position
|
||||
* @param items
|
||||
* @return
|
||||
private JList<CompletionCandidate> createSuggestionList(final int position,
|
||||
final DefaultListModel<CompletionCandidate> items) {
|
||||
|
||||
JList<CompletionCandidate> list = new JList<CompletionCandidate>(items);
|
||||
//list.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
|
||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
list.setSelectedIndex(0);
|
||||
list.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
insertSelection(MOUSE_COMPLETION);
|
||||
setInvisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
list.setCellRenderer(new CustomListRenderer());
|
||||
list.setFocusable(false);
|
||||
return list;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
// possibly defunct
|
||||
private boolean updateList(final DefaultListModel<CompletionCandidate> items, String newSubword,
|
||||
final Point location, int position) {
|
||||
this.subWord = new String(newSubword);
|
||||
if (subWord.indexOf('.') != -1)
|
||||
this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1);
|
||||
insertionPosition = position;
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scrollPane.getViewport().removeAll();
|
||||
completionList.setModel(items);
|
||||
completionList.setSelectedIndex(0);
|
||||
scrollPane.setViewportView(completionList);
|
||||
popupMenu.setPopupSize(calcWidth(), calcHeight(items.getSize()));
|
||||
//log("Suggestion updated" + System.nanoTime());
|
||||
textarea.requestFocusInWindow();
|
||||
popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0)
|
||||
+ location.y);
|
||||
completionList.validate();
|
||||
scrollPane.validate();
|
||||
popupMenu.validate();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Inserts the CompletionCandidate chosen from the suggestion list
|
||||
* @param completionSource - whether being completed via keypress or mouse click.
|
||||
* @return true - if code was successfully inserted at the caret position
|
||||
*/
|
||||
protected boolean insertSelection(int completionSource) {
|
||||
if (completionList.getSelectedValue() != null) {
|
||||
try {
|
||||
// If user types 'abc.', subword becomes '.' and null is returned
|
||||
String currentSubword = fetchCurrentSubword();
|
||||
int currentSubwordLen =
|
||||
(currentSubword == null) ? 0 : currentSubword.length();
|
||||
//logE(currentSubword + " <= subword,len => " + currentSubword.length());
|
||||
String selectedSuggestion =
|
||||
completionList.getSelectedValue().getCompletionString();
|
||||
|
||||
if (currentSubword != null) {
|
||||
selectedSuggestion = selectedSuggestion.substring(currentSubwordLen);
|
||||
} else {
|
||||
currentSubword = "";
|
||||
}
|
||||
|
||||
String completionString =
|
||||
completionList.getSelectedValue().getCompletionString();
|
||||
if (selectedSuggestion.endsWith(" )")) { // the case of single param methods
|
||||
// selectedSuggestion = ")";
|
||||
if (completionString.endsWith(" )")) {
|
||||
completionString =
|
||||
completionString.substring(0, completionString.length() - 2) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
boolean mouseClickOnOverloadedMethods = false;
|
||||
if (completionSource == MOUSE_COMPLETION) {
|
||||
// The case of overloaded methods, displayed as 'foo(...)'
|
||||
// They have completion strings as 'foo('. See #2755
|
||||
if (completionString.endsWith("(")) {
|
||||
mouseClickOnOverloadedMethods = true;
|
||||
}
|
||||
}
|
||||
|
||||
Messages.loge(subWord + " <= subword, Inserting suggestion=> " +
|
||||
selectedSuggestion + " Current sub: " + currentSubword);
|
||||
if (currentSubword.length() > 0) {
|
||||
textarea.getDocument().remove(insertionPosition - currentSubwordLen,
|
||||
currentSubwordLen);
|
||||
}
|
||||
|
||||
textarea.getDocument().insertString(insertionPosition - currentSubwordLen,
|
||||
completionString, null);
|
||||
if (selectedSuggestion.endsWith(")") && !selectedSuggestion.endsWith("()")) {
|
||||
// place the caret between '( and first ','
|
||||
int x = selectedSuggestion.indexOf(',');
|
||||
if (x == -1) {
|
||||
// the case of single param methods, containing no ','
|
||||
textarea.setCaretPosition(textarea.getCaretPosition() - 1); // just before ')'
|
||||
} else {
|
||||
textarea.setCaretPosition(insertionPosition + x);
|
||||
}
|
||||
}
|
||||
|
||||
Messages.log("Suggestion inserted: " + System.currentTimeMillis());
|
||||
if (completionList.getSelectedValue().getLabel().contains("...")) {
|
||||
// log("No hide");
|
||||
// Why not hide it? Coz this is the case of
|
||||
// overloaded methods. See #2755
|
||||
} else {
|
||||
setInvisible();
|
||||
}
|
||||
|
||||
if (mouseClickOnOverloadedMethods) {
|
||||
// See #2755
|
||||
((JavaTextArea) editor.getTextArea()).fetchPhrase();
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (BadLocationException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
setInvisible();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private String fetchCurrentSubword() {
|
||||
//log("Entering fetchCurrentSubword");
|
||||
JEditTextArea ta = editor.getTextArea();
|
||||
int off = ta.getCaretPosition();
|
||||
//log2("off " + off);
|
||||
if (off < 0)
|
||||
return null;
|
||||
int line = ta.getCaretLine();
|
||||
if (line < 0)
|
||||
return null;
|
||||
String s = ta.getLineText(line);
|
||||
//log2("lin " + line);
|
||||
//log2(s + " len " + s.length());
|
||||
|
||||
int x = ta.getCaretPosition() - ta.getLineStartOffset(line) - 1, x1 = x - 1;
|
||||
if (x >= s.length() || x < 0)
|
||||
return null; //TODO: Does this check cause problems? Verify.
|
||||
if (Base.DEBUG) System.out.print(" x char: " + s.charAt(x));
|
||||
//int xLS = off - getLineStartNonWhiteSpaceOffset(line);
|
||||
|
||||
String word = (x < s.length() ? s.charAt(x) : "") + "";
|
||||
if (s.trim().length() == 1) {
|
||||
// word = ""
|
||||
// + (keyChar == KeyEvent.CHAR_UNDEFINED ? s.charAt(x - 1) : keyChar);
|
||||
//word = (x < s.length()?s.charAt(x):"") + "";
|
||||
word = word.trim();
|
||||
if (word.endsWith("."))
|
||||
word = word.substring(0, word.length() - 1);
|
||||
|
||||
return word;
|
||||
}
|
||||
//log("fetchCurrentSubword 1 " + word);
|
||||
if(word.equals(".")) return null; // If user types 'abc.', subword becomes '.'
|
||||
// if (keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE)
|
||||
// ; // accepted these keys
|
||||
// else if (!(Character.isLetterOrDigit(keyChar) || keyChar == '_' || keyChar == '$'))
|
||||
// return null;
|
||||
int i = 0;
|
||||
|
||||
while (true) {
|
||||
i++;
|
||||
//TODO: currently works on single line only. "a. <new line> b()" won't be detected
|
||||
if (x1 >= 0) {
|
||||
// if (s.charAt(x1) != ';' && s.charAt(x1) != ',' && s.charAt(x1) != '(')
|
||||
if (Character.isLetterOrDigit(s.charAt(x1)) || s.charAt(x1) == '_') {
|
||||
|
||||
word = s.charAt(x1--) + word;
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (i > 200) {
|
||||
// time out!
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if (keyChar != KeyEvent.CHAR_UNDEFINED)
|
||||
//log("fetchCurrentSubword 2 " + word);
|
||||
if (Character.isDigit(word.charAt(0)))
|
||||
return null;
|
||||
word = word.trim();
|
||||
if (word.endsWith("."))
|
||||
word = word.substring(0, word.length() - 1);
|
||||
//log("fetchCurrentSubword 3 " + word);
|
||||
//showSuggestionLater();
|
||||
return word;
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When up arrow key is pressed, moves the highlighted selection up in the list
|
||||
*/
|
||||
protected void moveUp() {
|
||||
if (completionList.getSelectedIndex() == 0) {
|
||||
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
|
||||
selectIndex(completionList.getModel().getSize() - 1);
|
||||
|
||||
} else {
|
||||
int index = Math.max(completionList.getSelectedIndex() - 1, 0);
|
||||
selectIndex(index);
|
||||
int step = scrollPane.getVerticalScrollBar().getMaximum()
|
||||
/ completionList.getModel().getSize();
|
||||
scrollPane.getVerticalScrollBar().setValue(scrollPane
|
||||
.getVerticalScrollBar()
|
||||
.getValue()
|
||||
- step);
|
||||
// TODO: update JavaDoc to completionList.getSelectedValue()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When down arrow key is pressed, moves the highlighted selection down in the list
|
||||
*/
|
||||
protected void moveDown() {
|
||||
if (completionList.getSelectedIndex() == completionList.getModel().getSize() - 1) {
|
||||
scrollPane.getVerticalScrollBar().setValue(0);
|
||||
selectIndex(0);
|
||||
|
||||
} else {
|
||||
int index = Math.min(completionList.getSelectedIndex() + 1,
|
||||
completionList.getModel().getSize() - 1);
|
||||
selectIndex(index);
|
||||
// TODO: update JavaDoc to completionList.getSelectedValue()
|
||||
int step = scrollPane.getVerticalScrollBar().getMaximum() / completionList.getModel().getSize();
|
||||
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getValue() + step);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void selectIndex(int index) {
|
||||
completionList.setSelectedIndex(index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Custom cell renderer to display icons along with the completion candidates
|
||||
* @author Manindra Moharana <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
private static class CustomListRenderer extends javax.swing.DefaultListCellRenderer {
|
||||
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value,
|
||||
int index,
|
||||
boolean isSelected,
|
||||
boolean cellHasFocus) {
|
||||
JLabel label = (JLabel) super.getListCellRendererComponent(list, value,
|
||||
index,
|
||||
isSelected,
|
||||
cellHasFocus);
|
||||
if (isSelected) {
|
||||
label.setBackground(selectionBgColor);
|
||||
}
|
||||
label.setForeground(textColor);
|
||||
|
||||
if (value instanceof CompletionCandidate) {
|
||||
CompletionCandidate cc = (CompletionCandidate) value;
|
||||
switch (cc.getType()) {
|
||||
case CompletionCandidate.LOCAL_VAR:
|
||||
label.setIcon(localVarIcon);
|
||||
break;
|
||||
case CompletionCandidate.LOCAL_FIELD:
|
||||
case CompletionCandidate.PREDEF_FIELD:
|
||||
label.setIcon(fieldIcon);
|
||||
break;
|
||||
case CompletionCandidate.LOCAL_METHOD:
|
||||
case CompletionCandidate.PREDEF_METHOD:
|
||||
label.setIcon(methodIcon);
|
||||
break;
|
||||
case CompletionCandidate.LOCAL_CLASS:
|
||||
case CompletionCandidate.PREDEF_CLASS:
|
||||
label.setIcon(classIcon);
|
||||
break;
|
||||
|
||||
default:
|
||||
Messages.log("(CustomListRenderer)Unknown CompletionCandidate type " + cc.getType());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Messages.log("(CustomListRenderer)Unknown CompletionCandidate object " + value);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
|
||||
import processing.app.Messages;
|
||||
import processing.app.ui.ZoomTreeCellRenderer;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
|
||||
|
||||
class DebugTree {
|
||||
final JDialog window;
|
||||
final JTree tree;
|
||||
final Consumer<PreprocessedSketch> updateListener;
|
||||
|
||||
|
||||
DebugTree(JavaEditor editor, PreprocessingService pps) {
|
||||
updateListener = this::buildAndUpdateTree;
|
||||
|
||||
window = new JDialog(editor);
|
||||
|
||||
tree = new JTree() {
|
||||
@Override
|
||||
public String convertValueToText(Object value, boolean selected,
|
||||
boolean expanded, boolean leaf,
|
||||
int row, boolean hasFocus) {
|
||||
if (value instanceof DefaultMutableTreeNode) {
|
||||
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
|
||||
Object o = treeNode.getUserObject();
|
||||
if (o instanceof ASTNode) {
|
||||
ASTNode node = (ASTNode) o;
|
||||
return CompletionGenerator.getNodeAsString(node);
|
||||
}
|
||||
}
|
||||
return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
|
||||
}
|
||||
};
|
||||
tree.setCellRenderer(new ZoomTreeCellRenderer(editor.getMode()));
|
||||
window.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
pps.unregisterListener(updateListener);
|
||||
tree.setModel(null);
|
||||
}
|
||||
});
|
||||
window.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
|
||||
window.setBounds(new Rectangle(680, 100, 460, 620));
|
||||
window.setTitle("AST View - " + editor.getSketch().getName());
|
||||
JScrollPane sp = new JScrollPane();
|
||||
sp.setViewportView(tree);
|
||||
window.add(sp);
|
||||
pps.whenDone(updateListener);
|
||||
pps.registerListener(updateListener);
|
||||
|
||||
tree.addTreeSelectionListener(e -> {
|
||||
if (tree.getLastSelectedPathComponent() != null) {
|
||||
DefaultMutableTreeNode tnode =
|
||||
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
|
||||
if (tnode.getUserObject() instanceof ASTNode) {
|
||||
ASTNode node = (ASTNode) tnode.getUserObject();
|
||||
pps.whenDone(ps -> {
|
||||
SketchInterval si = ps.mapJavaToSketch(node);
|
||||
if (!ps.inRange(si)) return;
|
||||
EventQueue.invokeLater(() -> {
|
||||
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void dispose() {
|
||||
if (window != null) {
|
||||
window.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Thread: worker
|
||||
void buildAndUpdateTree(PreprocessedSketch ps) {
|
||||
CompilationUnit cu = ps.compilationUnit;
|
||||
if (cu.types().isEmpty()){
|
||||
Messages.loge("No Type found in CU");
|
||||
return;
|
||||
}
|
||||
|
||||
Deque<DefaultMutableTreeNode> treeNodeStack = new ArrayDeque<>();
|
||||
|
||||
ASTNode type0 = (ASTNode) cu.types().get(0);
|
||||
type0.accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean preVisit2(ASTNode node) {
|
||||
treeNodeStack.push(new DefaultMutableTreeNode(node));
|
||||
return super.preVisit2(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postVisit(ASTNode node) {
|
||||
if (treeNodeStack.size() > 1) {
|
||||
DefaultMutableTreeNode treeNode = treeNodeStack.pop();
|
||||
treeNodeStack.peek().add(treeNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
DefaultMutableTreeNode codeTree = treeNodeStack.pop();
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
if (tree.hasFocus() || window.hasFocus()) {
|
||||
return;
|
||||
}
|
||||
tree.setModel(new DefaultTreeModel(codeTree));
|
||||
((DefaultTreeModel) tree.getModel()).reload();
|
||||
tree.validate();
|
||||
if (!window.isVisible()) {
|
||||
window.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
|
||||
import com.google.classpath.ClassPath;
|
||||
import com.google.classpath.ClassPathFactory;
|
||||
import com.google.classpath.RegExpResourceFilter;
|
||||
|
||||
import processing.app.Language;
|
||||
import processing.app.Problem;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
|
||||
|
||||
class ErrorChecker {
|
||||
// Delay delivering error check result after last sketch change #2677
|
||||
private final static long DELAY_BEFORE_UPDATE = 650;
|
||||
|
||||
private ScheduledExecutorService scheduler;
|
||||
private volatile ScheduledFuture<?> scheduledUiUpdate = null;
|
||||
private volatile long nextUiUpdate = 0;
|
||||
private volatile boolean enabled = true;
|
||||
|
||||
private final Consumer<PreprocessedSketch> errorHandlerListener = this::handleSketchProblems;
|
||||
|
||||
private JavaEditor editor;
|
||||
private PreprocessingService pps;
|
||||
|
||||
|
||||
public ErrorChecker(JavaEditor editor, PreprocessingService pps) {
|
||||
this.editor = editor;
|
||||
this.pps = pps;
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
this.enabled = JavaMode.errorCheckEnabled;
|
||||
if (enabled) {
|
||||
pps.registerListener(errorHandlerListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void notifySketchChanged() {
|
||||
nextUiUpdate = System.currentTimeMillis() + DELAY_BEFORE_UPDATE;
|
||||
}
|
||||
|
||||
|
||||
public void preferencesChanged() {
|
||||
if (enabled != JavaMode.errorCheckEnabled) {
|
||||
enabled = JavaMode.errorCheckEnabled;
|
||||
if (enabled) {
|
||||
pps.registerListener(errorHandlerListener);
|
||||
} else {
|
||||
pps.unregisterListener(errorHandlerListener);
|
||||
editor.setProblemList(Collections.emptyList());
|
||||
nextUiUpdate = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
if (scheduler != null) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleSketchProblems(PreprocessedSketch ps) {
|
||||
Map<String, String[]> suggCache =
|
||||
JavaMode.importSuggestEnabled ? new HashMap<>() : Collections.emptyMap();
|
||||
|
||||
final List<Problem> problems = new ArrayList<>();
|
||||
|
||||
IProblem[] iproblems = ps.compilationUnit.getProblems();
|
||||
|
||||
{ // Check for curly quotes
|
||||
List<JavaProblem> curlyQuoteProblems = checkForCurlyQuotes(ps);
|
||||
problems.addAll(curlyQuoteProblems);
|
||||
}
|
||||
|
||||
if (problems.isEmpty()) { // Check for missing braces
|
||||
List<JavaProblem> missingBraceProblems = checkForMissingBraces(ps);
|
||||
problems.addAll(missingBraceProblems);
|
||||
}
|
||||
|
||||
if (problems.isEmpty()) {
|
||||
AtomicReference<ClassPath> searchClassPath = new AtomicReference<>(null);
|
||||
List<Problem> cuProblems = Arrays.stream(iproblems)
|
||||
// Filter Warnings if they are not enabled
|
||||
.filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled))
|
||||
// Hide a useless error which is produced when a line ends with
|
||||
// an identifier without a semicolon. "Missing a semicolon" is
|
||||
// also produced and is preferred over this one.
|
||||
// (Syntax error, insert ":: IdentifierOrNew" to complete Expression)
|
||||
// See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405780
|
||||
.filter(iproblem -> !iproblem.getMessage()
|
||||
.contains("Syntax error, insert \":: IdentifierOrNew\""))
|
||||
// Transform into our Problems
|
||||
.map(iproblem -> {
|
||||
JavaProblem p = convertIProblem(iproblem, ps);
|
||||
|
||||
// Handle import suggestions
|
||||
if (p != null && JavaMode.importSuggestEnabled && isUndefinedTypeProblem(iproblem)) {
|
||||
ClassPath cp = searchClassPath.updateAndGet(prev -> prev != null ?
|
||||
prev : new ClassPathFactory().createFromPaths(ps.searchClassPathArray));
|
||||
String[] s = suggCache.computeIfAbsent(iproblem.getArguments()[0],
|
||||
name -> getImportSuggestions(cp, name));
|
||||
p.setImportSuggestions(s);
|
||||
}
|
||||
|
||||
return p;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
problems.addAll(cuProblems);
|
||||
}
|
||||
|
||||
if (scheduledUiUpdate != null) {
|
||||
scheduledUiUpdate.cancel(true);
|
||||
}
|
||||
// Update UI after a delay. See #2677
|
||||
long delay = nextUiUpdate - System.currentTimeMillis();
|
||||
Runnable uiUpdater = () -> {
|
||||
if (nextUiUpdate > 0 && System.currentTimeMillis() >= nextUiUpdate) {
|
||||
EventQueue.invokeLater(() -> editor.setProblemList(problems));
|
||||
}
|
||||
};
|
||||
scheduledUiUpdate = scheduler.schedule(uiUpdater, delay,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
static private JavaProblem convertIProblem(IProblem iproblem, PreprocessedSketch ps) {
|
||||
SketchInterval in = ps.mapJavaToSketch(iproblem);
|
||||
if (in == SketchInterval.BEFORE_START) return null;
|
||||
String badCode = ps.getPdeCode(in);
|
||||
int line = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
|
||||
JavaProblem p = JavaProblem.fromIProblem(iproblem, in.tabIndex, line, badCode);
|
||||
p.setPDEOffsets(in.startTabOffset, in.stopTabOffset);
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
static private boolean isUndefinedTypeProblem(IProblem iproblem) {
|
||||
int id = iproblem.getID();
|
||||
return id == IProblem.UndefinedType ||
|
||||
id == IProblem.UndefinedName ||
|
||||
id == IProblem.UnresolvedVariable;
|
||||
}
|
||||
|
||||
|
||||
static private boolean isMissingBraceProblem(IProblem iproblem) {
|
||||
switch (iproblem.getID()) {
|
||||
case IProblem.ParsingErrorInsertToComplete: {
|
||||
char brace = iproblem.getArguments()[0].charAt(0);
|
||||
return brace == '{' || brace == '}';
|
||||
}
|
||||
case IProblem.ParsingErrorInsertTokenAfter: {
|
||||
char brace = iproblem.getArguments()[1].charAt(0);
|
||||
return brace == '{' || brace == '}';
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final Pattern CURLY_QUOTE_REGEX =
|
||||
Pattern.compile("([“”‘’])", Pattern.UNICODE_CHARACTER_CLASS);
|
||||
|
||||
static private List<JavaProblem> checkForCurlyQuotes(PreprocessedSketch ps) {
|
||||
List<JavaProblem> problems = new ArrayList<>(0);
|
||||
|
||||
// Go through the scrubbed code and look for curly quotes (they should not be any)
|
||||
Matcher matcher = CURLY_QUOTE_REGEX.matcher(ps.scrubbedPdeCode);
|
||||
while (matcher.find()) {
|
||||
int pdeOffset = matcher.start();
|
||||
String q = matcher.group();
|
||||
|
||||
int tabIndex = ps.pdeOffsetToTabIndex(pdeOffset);
|
||||
int tabOffset = ps.pdeOffsetToTabOffset(tabIndex, pdeOffset);
|
||||
int tabLine = ps.tabOffsetToTabLine(tabIndex, tabOffset);
|
||||
|
||||
String message = Language.interpolate("editor.status.bad_curly_quote", q);
|
||||
JavaProblem problem = new JavaProblem(message, JavaProblem.ERROR, tabIndex, tabLine);
|
||||
problem.setPDEOffsets(tabOffset, tabOffset+1);
|
||||
|
||||
problems.add(problem);
|
||||
}
|
||||
|
||||
|
||||
// Go through iproblems and look for problems involving curly quotes
|
||||
List<JavaProblem> problems2 = new ArrayList<>(0);
|
||||
IProblem[] iproblems = ps.compilationUnit.getProblems();
|
||||
|
||||
for (IProblem iproblem : iproblems) {
|
||||
switch (iproblem.getID()) {
|
||||
case IProblem.ParsingErrorDeleteToken:
|
||||
case IProblem.ParsingErrorDeleteTokens:
|
||||
case IProblem.ParsingErrorInvalidToken:
|
||||
case IProblem.ParsingErrorReplaceTokens:
|
||||
case IProblem.UnterminatedString:
|
||||
SketchInterval in = ps.mapJavaToSketch(iproblem);
|
||||
if (in == SketchInterval.BEFORE_START) continue;
|
||||
String badCode = ps.getPdeCode(in);
|
||||
matcher.reset(badCode);
|
||||
while (matcher.find()) {
|
||||
int offset = matcher.start();
|
||||
String q = matcher.group();
|
||||
int tabStart = in.startTabOffset + offset;
|
||||
int tabStop = tabStart + 1;
|
||||
// Prevent duplicate problems
|
||||
if (problems.stream().noneMatch(p -> p.getStartOffset() == tabStart)) {
|
||||
int line = ps.tabOffsetToTabLine(in.tabIndex, tabStart);
|
||||
String message;
|
||||
if (iproblem.getID() == IProblem.UnterminatedString) {
|
||||
message = Language.interpolate("editor.status.unterm_string_curly", q);
|
||||
} else {
|
||||
message = Language.interpolate("editor.status.bad_curly_quote", q);
|
||||
}
|
||||
JavaProblem p = new JavaProblem(message, JavaProblem.ERROR, in.tabIndex, line);
|
||||
p.setPDEOffsets(tabStart, tabStop);
|
||||
problems2.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
problems.addAll(problems2);
|
||||
|
||||
return problems;
|
||||
}
|
||||
|
||||
|
||||
static private List<JavaProblem> checkForMissingBraces(PreprocessedSketch ps) {
|
||||
List<JavaProblem> problems = new ArrayList<>(0);
|
||||
for (int tabIndex = 0; tabIndex < ps.tabStartOffsets.length; tabIndex++) {
|
||||
int tabStartOffset = ps.tabStartOffsets[tabIndex];
|
||||
int tabEndOffset = (tabIndex < ps.tabStartOffsets.length - 1) ?
|
||||
ps.tabStartOffsets[tabIndex + 1] : ps.scrubbedPdeCode.length();
|
||||
int[] braceResult = SourceUtils.checkForMissingBraces(ps.scrubbedPdeCode, tabStartOffset, tabEndOffset);
|
||||
if (braceResult[0] != 0) {
|
||||
JavaProblem problem =
|
||||
new JavaProblem(braceResult[0] < 0
|
||||
? Language.interpolate("editor.status.missing.left_curly_bracket")
|
||||
: Language.interpolate("editor.status.missing.right_curly_bracket"),
|
||||
JavaProblem.ERROR, tabIndex, braceResult[1]);
|
||||
problem.setPDEOffsets(braceResult[3], braceResult[3] + 1);
|
||||
problems.add(problem);
|
||||
}
|
||||
}
|
||||
|
||||
if (problems.isEmpty()) {
|
||||
return problems;
|
||||
}
|
||||
|
||||
int problemTabIndex = problems.get(0).getTabIndex();
|
||||
|
||||
IProblem missingBraceProblem = Arrays.stream(ps.compilationUnit.getProblems())
|
||||
.filter(ErrorChecker::isMissingBraceProblem)
|
||||
// Ignore if it is at the end of file
|
||||
.filter(p -> p.getSourceEnd() + 1 < ps.javaCode.length())
|
||||
// Ignore if the tab number does not match our detected tab number
|
||||
.filter(p -> problemTabIndex == ps.mapJavaToSketch(p).tabIndex)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
// Prefer ECJ problem, shows location more accurately
|
||||
if (missingBraceProblem != null) {
|
||||
JavaProblem p = convertIProblem(missingBraceProblem, ps);
|
||||
if (p != null) {
|
||||
problems.clear();
|
||||
problems.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
return problems;
|
||||
}
|
||||
|
||||
|
||||
static public String[] getImportSuggestions(ClassPath cp, String className) {
|
||||
className = className.replace("[", "\\[").replace("]", "\\]");
|
||||
RegExpResourceFilter regf = new RegExpResourceFilter(
|
||||
Pattern.compile(".*"),
|
||||
Pattern.compile("(.*\\$)?" + className + "\\.class",
|
||||
Pattern.CASE_INSENSITIVE));
|
||||
|
||||
String[] resources = cp.findResources("", regf);
|
||||
return Arrays.stream(resources)
|
||||
// remove ".class" suffix
|
||||
.map(res -> res.substring(0, res.length() - 6))
|
||||
// replace path separators with dots
|
||||
.map(res -> res.replace('/', '.'))
|
||||
// replace inner class separators with dots
|
||||
.map(res -> res.replace('$', '.'))
|
||||
// sort, prioritize clases from java. package
|
||||
.sorted((o1, o2) -> {
|
||||
// put java.* first, should be prioritized more
|
||||
boolean o1StartsWithJava = o1.startsWith("java");
|
||||
boolean o2StartsWithJava = o2.startsWith("java");
|
||||
if (o1StartsWithJava != o2StartsWithJava) {
|
||||
if (o1StartsWithJava) return -1;
|
||||
return 1;
|
||||
}
|
||||
return o1.compareTo(o2);
|
||||
})
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
|
||||
|
||||
import processing.app.Language;
|
||||
import processing.app.Messages;
|
||||
import processing.core.PApplet;
|
||||
import processing.data.StringList;
|
||||
|
||||
|
||||
public class ErrorMessageSimplifier {
|
||||
/**
|
||||
* Mapping between ProblemID constant and the constant name. Holds about 650
|
||||
* of them. Also, this is just temporary, will be used to find the common
|
||||
* error types, cos you know, identifying String names is easier than
|
||||
* identifying 8 digit int constants!
|
||||
* TODO: this is temporary
|
||||
*/
|
||||
private static TreeMap<Integer, String> constantsMap;
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final Pattern tokenRegExp = Pattern.compile("\\b token\\b");
|
||||
|
||||
private static void prepareConstantsList() {
|
||||
constantsMap = new TreeMap<>();
|
||||
Class<DefaultProblem> probClass = DefaultProblem.class;
|
||||
Field f[] = probClass.getFields();
|
||||
for (Field field : f) {
|
||||
if (Modifier.isStatic(field.getModifiers()))
|
||||
try {
|
||||
if (DEBUG) {
|
||||
Messages.log(field.getName() + " :" + field.get(null));
|
||||
}
|
||||
Object val = field.get(null);
|
||||
if (val instanceof Integer) {
|
||||
constantsMap.put((Integer) (val), field.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Messages.log("Total items: " + constantsMap.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String getIDName(int id) {
|
||||
if (constantsMap == null) {
|
||||
prepareConstantsList();
|
||||
}
|
||||
return constantsMap.get(id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tones down the jargon in the ecj reported errors.
|
||||
*/
|
||||
public static String getSimplifiedErrorMessage(IProblem iprob, String badCode) {
|
||||
if (iprob == null) return null;
|
||||
|
||||
String args[] = iprob.getArguments();
|
||||
|
||||
if (DEBUG) {
|
||||
Messages.log("Simplifying message: " + iprob.getMessage() +
|
||||
" ID: " + getIDName(iprob.getID()));
|
||||
Messages.log("Arg count: " + args.length);
|
||||
for (String arg : args) {
|
||||
Messages.log("Arg " + arg);
|
||||
}
|
||||
Messages.log("Bad code: " + badCode);
|
||||
}
|
||||
|
||||
String result = null;
|
||||
|
||||
switch (iprob.getID()) {
|
||||
|
||||
case IProblem.ParsingError:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.error_on", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.ParsingErrorDeleteToken:
|
||||
if (args.length > 0) {
|
||||
if (args[0].equalsIgnoreCase("Invalid Character")) {
|
||||
result = getErrorMessageForCurlyQuote(badCode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.ParsingErrorDeleteTokens:
|
||||
result = getErrorMessageForCurlyQuote(badCode);
|
||||
if (result == null) {
|
||||
result = Language.interpolate("editor.status.error_on", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.ParsingErrorInsertToComplete:
|
||||
if (args.length > 0) {
|
||||
if (args[0].length() == 1) {
|
||||
result = getErrorMessageForBracket(args[0].charAt(0));
|
||||
|
||||
} else {
|
||||
if (args[0].equals("AssignmentOperator Expression")) {
|
||||
result = Language.interpolate("editor.status.missing.add", "=");
|
||||
|
||||
} else if (args[0].equalsIgnoreCase(") Statement")) {
|
||||
result = getErrorMessageForBracket(args[0].charAt(0));
|
||||
|
||||
} else {
|
||||
result = Language.interpolate("editor.status.error_on", args[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.ParsingErrorInvalidToken:
|
||||
if (args.length > 0) {
|
||||
if (args[0].equals("int")) {
|
||||
if (args[1].equals("VariableDeclaratorId")) {
|
||||
result = Language.text("editor.status.reserved_words");
|
||||
} else {
|
||||
result = Language.interpolate("editor.status.error_on", args[0]);
|
||||
}
|
||||
} else if (args[0].equalsIgnoreCase("Invalid Character")) {
|
||||
result = getErrorMessageForCurlyQuote(badCode);
|
||||
}
|
||||
if (result == null) {
|
||||
result = Language.interpolate("editor.status.error_on", args[0]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.ParsingErrorInsertTokenAfter:
|
||||
if (args.length > 0) {
|
||||
if (args[1].length() == 1) {
|
||||
result = getErrorMessageForBracket(args[1].charAt(0));
|
||||
} else {
|
||||
// https://github.com/processing/processing/issues/3104
|
||||
if (args[1].equalsIgnoreCase("Statement")) {
|
||||
result = Language.interpolate("editor.status.error_on", args[0]);
|
||||
} else {
|
||||
result =
|
||||
Language.interpolate("editor.status.error_on", args[0]) + " " +
|
||||
Language.interpolate("editor.status.missing.add", args[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.ParsingErrorReplaceTokens:
|
||||
result = getErrorMessageForCurlyQuote(badCode);
|
||||
|
||||
case IProblem.UndefinedConstructor:
|
||||
if (args.length == 2) {
|
||||
String constructorName = args[0];
|
||||
// For messages such as "contructor sketch_name.ClassXYZ() is undefined", change
|
||||
// constructor name to "ClassXYZ()". See #3434
|
||||
if (constructorName.contains(".")) {
|
||||
// arg[0] contains sketch name twice: sketch_150705a.sketch_150705a.Thing
|
||||
constructorName = constructorName.substring(constructorName.indexOf('.') + 1);
|
||||
constructorName = constructorName.substring(constructorName.indexOf('.') + 1);
|
||||
}
|
||||
String constructorArgs = removePackagePrefixes(args[args.length - 1]);
|
||||
result = Language.interpolate("editor.status.undefined_constructor", constructorName, constructorArgs);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.UndefinedMethod:
|
||||
if (args.length > 2) {
|
||||
String methodName = args[args.length - 2];
|
||||
String methodArgs = removePackagePrefixes(args[args.length - 1]);
|
||||
result = Language.interpolate("editor.status.undefined_method", methodName, methodArgs);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.ParameterMismatch:
|
||||
if (args.length > 3) {
|
||||
// 2nd arg is method name, 3rd arg is correct param list
|
||||
if (args[2].trim().length() == 0) {
|
||||
// the case where no params are needed.
|
||||
result = Language.interpolate("editor.status.empty_param", args[1]);
|
||||
|
||||
} else {
|
||||
result = Language.interpolate("editor.status.wrong_param",
|
||||
args[1], args[1], removePackagePrefixes(args[2]));
|
||||
// String method = q(args[1]);
|
||||
// String methodDef = " \"" + args[1] + "(" + getSimpleName(args[2]) + ")\"";
|
||||
// result = result.replace("method", method);
|
||||
// result += methodDef;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.UndefinedField:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.undef_global_var", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.UndefinedType:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.undef_class", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.UnresolvedVariable:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.undef_var", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.UndefinedName:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.undef_name", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.UnterminatedString:
|
||||
if (badCode.contains("“") || badCode.contains("”")) {
|
||||
result = Language.interpolate("editor.status.unterm_string_curly",
|
||||
badCode.replaceAll("[^“”]", ""));
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.TypeMismatch:
|
||||
if (args.length > 1) {
|
||||
result = Language.interpolate("editor.status.type_mismatch", args[0], args[1]);
|
||||
// result = result.replace("typeA", q(args[0]));
|
||||
// result = result.replace("typeB", q(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.LocalVariableIsNeverUsed:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.unused_variable", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.UninitializedLocalVariable:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.uninitialized_variable", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.AssignmentHasNoEffect:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.no_effect_assignment", args[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IProblem.HidingEnclosingType:
|
||||
if (args.length > 0) {
|
||||
result = Language.interpolate("editor.status.hiding_enclosing_type", args[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
String message = iprob.getMessage();
|
||||
if (message != null) {
|
||||
// Remove all instances of token
|
||||
// "Syntax error on token 'blah', delete this token"
|
||||
Matcher matcher = tokenRegExp.matcher(message);
|
||||
message = matcher.replaceAll("");
|
||||
result = message;
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Messages.log("Simplified Error Msg: " + result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts java.lang.String into String, etc
|
||||
*/
|
||||
static private String removePackagePrefixes(String input) {
|
||||
if (!input.contains(".")) {
|
||||
return input;
|
||||
}
|
||||
String[] names = PApplet.split(input, ',');
|
||||
// List<String> names = new ArrayList<String>();
|
||||
// if (inp.indexOf(',') >= 0) {
|
||||
// names.addAll(Arrays.asList(inp.split(",")));
|
||||
// } else {
|
||||
// names.add(inp);
|
||||
// }
|
||||
StringList result = new StringList();
|
||||
for (String name : names) {
|
||||
int dot = name.lastIndexOf('.');
|
||||
if (dot >= 0) {
|
||||
name = name.substring(dot + 1, name.length());
|
||||
}
|
||||
result.append(name);
|
||||
}
|
||||
return result.join(", ");
|
||||
}
|
||||
|
||||
|
||||
static private String getErrorMessageForBracket(char c) {
|
||||
switch (c) {
|
||||
case ';': return Language.text("editor.status.missing.semicolon");
|
||||
case '[': return Language.text("editor.status.missing.left_sq_bracket");
|
||||
case ']': return Language.text("editor.status.missing.right_sq_bracket");
|
||||
case '(': return Language.text("editor.status.missing.left_paren");
|
||||
case ')': return Language.text("editor.status.missing.right_paren");
|
||||
case '{': return Language.text("editor.status.missing.left_curly_bracket");
|
||||
case '}': return Language.text("editor.status.missing.right_curly_bracket");
|
||||
}
|
||||
// This seems to be unreachable and wasn't in PDE.properties.
|
||||
// I've added it for 3.0a8, but that seems gross. [fry]
|
||||
return Language.interpolate("editor.status.missing.default", c);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param badCode The code which may contain curly quotes
|
||||
* @return Friendly error message if there is a curly quote in badCode,
|
||||
* null otherwise.
|
||||
*/
|
||||
static private String getErrorMessageForCurlyQuote(String badCode) {
|
||||
if (badCode.contains("‘") || badCode.contains("’") ||
|
||||
badCode.contains("“") || badCode.contains("”")) {
|
||||
return Language.interpolate("editor.status.bad_curly_quote",
|
||||
badCode.replaceAll("[^‘’“”]", ""));
|
||||
} else return null;
|
||||
}
|
||||
|
||||
|
||||
// static private final String q(Object quotable) {
|
||||
// return "\"" + quotable + "\"";
|
||||
// }
|
||||
|
||||
|
||||
// static private final String qs(Object quotable) {
|
||||
// return " " + q(quotable);
|
||||
// }
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* Wrapper for import statements
|
||||
*
|
||||
* @author Manindra Moharana <me@mkmoharana.com>
|
||||
*
|
||||
*/
|
||||
public class ImportStatement {
|
||||
|
||||
private static final String importKw = "import";
|
||||
private static final String staticKw = "static";
|
||||
|
||||
// private boolean isClass;
|
||||
private boolean isStarred;
|
||||
private boolean isStatic;
|
||||
|
||||
/**
|
||||
* Full class name of the import with all packages
|
||||
* Ends with star for starred imports
|
||||
*/
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* Name of the package e.g. everything before last dot
|
||||
*/
|
||||
private String packageName;
|
||||
|
||||
private ImportStatement() { }
|
||||
|
||||
public static ImportStatement wholePackage(String pckg) {
|
||||
ImportStatement is = new ImportStatement();
|
||||
is.packageName = pckg;
|
||||
is.className = "*";
|
||||
is.isStarred = true;
|
||||
return is;
|
||||
}
|
||||
|
||||
public static ImportStatement singleClass(String cls) {
|
||||
ImportStatement is = new ImportStatement();
|
||||
int lastDot = cls.lastIndexOf('.');
|
||||
is.className = lastDot >= 0 ? cls.substring(lastDot+1) : cls;
|
||||
is.packageName = lastDot >= 0 ? cls.substring(0, lastDot) : "";
|
||||
// is.isClass = true;
|
||||
return is;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static ImportStatement parse(String importString) {
|
||||
Matcher matcher = SourceUtils.IMPORT_REGEX_NO_KEYWORD.matcher(importString);
|
||||
if (!matcher.find()) return null;
|
||||
|
||||
return parse(matcher.toMatchResult());
|
||||
}
|
||||
|
||||
public static ImportStatement parse(MatchResult match) {
|
||||
ImportStatement is = new ImportStatement();
|
||||
|
||||
is.isStatic = match.group(2) != null;
|
||||
String pckg = match.group(3);
|
||||
pckg = (pckg == null) ? "" : pckg.replaceAll("\\s","");
|
||||
is.packageName = pckg.endsWith(".") ?
|
||||
pckg.substring(0, pckg.length()-1) :
|
||||
pckg;
|
||||
|
||||
is.className = match.group(4);
|
||||
is.isStarred = is.className.equals("*");
|
||||
|
||||
return is;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getFullSourceLine() {
|
||||
return importKw + " " + (isStatic ? (staticKw + " ") : "") + packageName + "." + className + ";";
|
||||
}
|
||||
|
||||
public String getFullClassName(){
|
||||
return packageName + "." + className;
|
||||
}
|
||||
|
||||
public String getClassName(){
|
||||
return className;
|
||||
}
|
||||
|
||||
public String getPackageName(){
|
||||
return packageName;
|
||||
}
|
||||
|
||||
public boolean isStarredImport() {
|
||||
return isStarred;
|
||||
}
|
||||
|
||||
public boolean isStaticImport() {
|
||||
return isStatic;
|
||||
}
|
||||
|
||||
public boolean isSameAs(ImportStatement is) {
|
||||
return packageName.equals(is.packageName) &&
|
||||
className.equals(is.className) &&
|
||||
isStatic == is.isStatic;
|
||||
}
|
||||
}
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.IBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.jdt.core.dom.VariableDeclaration;
|
||||
|
||||
import processing.app.Language;
|
||||
import processing.app.Messages;
|
||||
import processing.app.Platform;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
|
||||
import static processing.mode.java.pdex.ASTUtils.getSimpleNameAt;
|
||||
import static processing.mode.java.pdex.ASTUtils.resolveBinding;
|
||||
|
||||
|
||||
class InspectMode {
|
||||
final JavaEditor editor;
|
||||
final PreprocessingService pps;
|
||||
final ShowUsage usage;
|
||||
|
||||
boolean inspectModeEnabled;
|
||||
|
||||
boolean isMouse1Down;
|
||||
boolean isMouse2Down;
|
||||
boolean isHotkeyDown;
|
||||
|
||||
Predicate<MouseEvent> mouseEventHotkeyTest = Platform.isMacOS() ?
|
||||
InputEvent::isMetaDown : InputEvent::isControlDown;
|
||||
Predicate<KeyEvent> keyEventHotkeyTest = Platform.isMacOS() ?
|
||||
e -> e.getKeyCode() == KeyEvent.VK_META :
|
||||
e -> e.getKeyCode() == KeyEvent.VK_CONTROL;
|
||||
|
||||
|
||||
InspectMode(JavaEditor editor, PreprocessingService pps, ShowUsage usage) {
|
||||
this.editor = editor;
|
||||
this.pps = pps;
|
||||
this.usage = usage;
|
||||
|
||||
// Add listeners
|
||||
|
||||
JMenuItem showUsageItem = new JMenuItem(Language.text("editor.popup.jump_to_declaration"));
|
||||
showUsageItem.addActionListener(e -> handleInspect());
|
||||
editor.getTextArea().getRightClickPopup().add(showUsageItem);
|
||||
|
||||
editor.getJavaTextArea().getPainter().addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
isMouse1Down = isMouse1Down || (e.getButton() == MouseEvent.BUTTON1);
|
||||
isMouse2Down = isMouse2Down || (e.getButton() == MouseEvent.BUTTON2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
boolean releasingMouse1 = e.getButton() == MouseEvent.BUTTON1;
|
||||
boolean releasingMouse2 = e.getButton() == MouseEvent.BUTTON2;
|
||||
if (JavaMode.inspectModeHotkeyEnabled && inspectModeEnabled &&
|
||||
isMouse1Down && releasingMouse1) {
|
||||
handleInspect(e);
|
||||
} else if (!inspectModeEnabled && isMouse2Down && releasingMouse2) {
|
||||
handleInspect(e);
|
||||
}
|
||||
isMouse1Down = isMouse1Down && !releasingMouse1;
|
||||
isMouse2Down = isMouse2Down && !releasingMouse2;
|
||||
}
|
||||
});
|
||||
|
||||
editor.getJavaTextArea().getPainter().addMouseMotionListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
if (editor.isSelectionActive()) {
|
||||
// Mouse was dragged too much, disable
|
||||
inspectModeEnabled = false;
|
||||
// Cancel possible mouse 2 press
|
||||
isMouse2Down = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
isMouse1Down = false;
|
||||
isMouse2Down = false;
|
||||
isHotkeyDown = mouseEventHotkeyTest.test(e);
|
||||
inspectModeEnabled = isHotkeyDown;
|
||||
}
|
||||
});
|
||||
|
||||
editor.getJavaTextArea().addMouseWheelListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e) {
|
||||
// Editor was scrolled while mouse 1 was pressed, disable
|
||||
if (isMouse1Down) inspectModeEnabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
editor.getJavaTextArea().addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
isHotkeyDown = isHotkeyDown || keyEventHotkeyTest.test(e);
|
||||
// Enable if hotkey was just pressed and mouse 1 is not down
|
||||
inspectModeEnabled = inspectModeEnabled || (!isMouse1Down && isHotkeyDown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
isHotkeyDown = isHotkeyDown && !keyEventHotkeyTest.test(e);
|
||||
// Disable if hotkey was just released
|
||||
inspectModeEnabled = inspectModeEnabled && isHotkeyDown;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void handleInspect() {
|
||||
int off = editor.getSelectionStart();
|
||||
int tabIndex = editor.getSketch().getCurrentCodeIndex();
|
||||
|
||||
pps.whenDoneBlocking(ps -> handleInspect(ps, tabIndex, off));
|
||||
}
|
||||
|
||||
|
||||
// Thread: EDT
|
||||
void handleInspect(MouseEvent evt) {
|
||||
int off = editor.getJavaTextArea().xyToOffset(evt.getX(), evt.getY());
|
||||
if (off < 0) return;
|
||||
int tabIndex = editor.getSketch().getCurrentCodeIndex();
|
||||
|
||||
pps.whenDoneBlocking(ps -> handleInspect(ps, tabIndex, off));
|
||||
}
|
||||
|
||||
|
||||
// Thread: worker
|
||||
private void handleInspect(PreprocessedSketch ps, int tabIndex, int offset) {
|
||||
ASTNode root = ps.compilationUnit;
|
||||
int javaOffset = ps.tabOffsetToJavaOffset(tabIndex, offset);
|
||||
|
||||
SimpleName simpleName = getSimpleNameAt(root, javaOffset, javaOffset);
|
||||
|
||||
if (simpleName == null) {
|
||||
Messages.log("no simple name found at click location");
|
||||
return;
|
||||
}
|
||||
|
||||
IBinding binding = resolveBinding(simpleName);
|
||||
if (binding == null) {
|
||||
Messages.log("binding not resolved");
|
||||
return;
|
||||
}
|
||||
|
||||
String key = binding.getKey();
|
||||
ASTNode decl = ps.compilationUnit.findDeclaringNode(key);
|
||||
if (decl == null) {
|
||||
Messages.log("decl not found, showing usage instead");
|
||||
usage.findUsageAndUpdateTree(ps, binding);
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleName declName = null;
|
||||
switch (binding.getKind()) {
|
||||
case IBinding.TYPE: declName = ((TypeDeclaration) decl).getName(); break;
|
||||
case IBinding.METHOD: declName = ((MethodDeclaration) decl).getName(); break;
|
||||
case IBinding.VARIABLE: declName = ((VariableDeclaration) decl).getName(); break;
|
||||
}
|
||||
if (declName == null) {
|
||||
Messages.log("decl name not found " + decl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (declName.equals(simpleName)) {
|
||||
usage.findUsageAndUpdateTree(ps, binding);
|
||||
} else {
|
||||
Messages.log("found declaration, offset " + decl.getStartPosition() + ", name: " + declName);
|
||||
SketchInterval si = ps.mapJavaToSketch(declName);
|
||||
if (!ps.inRange(si)) return;
|
||||
EventQueue.invokeLater(() -> {
|
||||
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void dispose() {
|
||||
// Nothing to do
|
||||
}
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
|
||||
import processing.app.Problem;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper class for IProblem that stores the tabIndex and line number
|
||||
* according to its tab, including the original IProblem object
|
||||
*/
|
||||
public class JavaProblem implements Problem {
|
||||
/**
|
||||
* The tab number to which the error belongs to
|
||||
*/
|
||||
private int tabIndex;
|
||||
/**
|
||||
* Line number(pde code) of the error
|
||||
*/
|
||||
private int lineNumber;
|
||||
|
||||
private int startOffset;
|
||||
|
||||
private int stopOffset;
|
||||
|
||||
/**
|
||||
* Error Message. Processed form of IProblem.getMessage()
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* The type of error - WARNING or ERROR.
|
||||
*/
|
||||
private int type;
|
||||
|
||||
/**
|
||||
* If the error is a 'cannot find type' contains the list of suggested imports
|
||||
*/
|
||||
private String[] importSuggestions;
|
||||
|
||||
public static final int ERROR = 1, WARNING = 2;
|
||||
|
||||
public JavaProblem(String message, int type, int tabIndex, int lineNumber) {
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
this.tabIndex = tabIndex;
|
||||
this.lineNumber = lineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param iProblem - The IProblem which is being wrapped
|
||||
* @param tabIndex - The tab number to which the error belongs to
|
||||
* @param lineNumber - Line number(pde code) of the error
|
||||
* @param badCode - The code iProblem refers to.
|
||||
*/
|
||||
public static JavaProblem fromIProblem(IProblem iProblem,
|
||||
int tabIndex, int lineNumber, String badCode) {
|
||||
int type = 0;
|
||||
if(iProblem.isError()) {
|
||||
type = ERROR;
|
||||
} else if (iProblem.isWarning()) {
|
||||
type = WARNING;
|
||||
}
|
||||
String message = ErrorMessageSimplifier.getSimplifiedErrorMessage(iProblem, badCode);
|
||||
return new JavaProblem(message, type, tabIndex, lineNumber);
|
||||
}
|
||||
|
||||
public void setPDEOffsets(int startOffset, int stopOffset){
|
||||
this.startOffset = startOffset;
|
||||
this.stopOffset = stopOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStartOffset() {
|
||||
return startOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStopOffset() {
|
||||
return stopOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isError() {
|
||||
return type == ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWarning() {
|
||||
return type == WARNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTabIndex() {
|
||||
return tabIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public String[] getImportSuggestions() {
|
||||
return importSuggestions;
|
||||
}
|
||||
|
||||
public void setImportSuggestions(String[] a) {
|
||||
importSuggestions = a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TAB " + tabIndex + ",LN " + lineNumber + "LN START OFF: "
|
||||
+ startOffset + ",LN STOP OFF: " + stopOffset + ",PROB: "
|
||||
+ message;
|
||||
}
|
||||
|
||||
}
|
||||
+644
@@ -0,0 +1,644 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.SwingWorker;
|
||||
|
||||
import processing.app.Messages;
|
||||
import processing.app.Platform;
|
||||
import processing.app.syntax.PdeTextArea;
|
||||
import processing.app.syntax.TextAreaDefaults;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.JavaInputHandler;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.tweak.ColorControlBox;
|
||||
import processing.mode.java.tweak.Handle;
|
||||
|
||||
|
||||
/**
|
||||
* TextArea implementation for Java Mode. Primary differences from PdeTextArea
|
||||
* are completions, suggestions, and tweak handling.
|
||||
*/
|
||||
public class JavaTextArea extends PdeTextArea {
|
||||
private CompletionPanel suggestion;
|
||||
|
||||
|
||||
public JavaTextArea(TextAreaDefaults defaults, JavaEditor editor) {
|
||||
super(defaults, new JavaInputHandler(editor), editor);
|
||||
|
||||
suggestionGenerator = new CompletionGenerator();
|
||||
|
||||
tweakMode = false;
|
||||
}
|
||||
|
||||
|
||||
public JavaEditor getJavaEditor() {
|
||||
return (JavaEditor) editor;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected JavaTextAreaPainter createPainter(final TextAreaDefaults defaults) {
|
||||
return new JavaTextAreaPainter(this, defaults);
|
||||
}
|
||||
|
||||
|
||||
// used by Tweak Mode
|
||||
protected JavaTextAreaPainter getJavaPainter() {
|
||||
return (JavaTextAreaPainter) painter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles KeyEvents for TextArea (code completion begins from here).
|
||||
* TODO Needs explanation of why this implemented with an override
|
||||
* of processKeyEvent() instead of using listeners.
|
||||
*/
|
||||
@Override
|
||||
public void processKeyEvent(KeyEvent evt) {
|
||||
if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
||||
if (suggestion != null){
|
||||
if (suggestion.isVisible()){
|
||||
Messages.log("ESC key");
|
||||
hideSuggestion();
|
||||
evt.consume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (evt.getKeyCode() == KeyEvent.VK_ENTER &&
|
||||
evt.getID() == KeyEvent.KEY_PRESSED) {
|
||||
if (suggestion != null &&
|
||||
suggestion.isVisible() &&
|
||||
suggestion.insertSelection(CompletionPanel.KEYBOARD_COMPLETION)) {
|
||||
evt.consume();
|
||||
// Still try to show suggestions after inserting if it's
|
||||
// the case of overloaded methods. See #2755
|
||||
if (suggestion.isVisible()) {
|
||||
prepareSuggestions(evt);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (evt.getID() == KeyEvent.KEY_PRESSED) {
|
||||
switch (evt.getKeyCode()) {
|
||||
case KeyEvent.VK_DOWN:
|
||||
if (suggestion != null)
|
||||
if (suggestion.isVisible()) {
|
||||
//log("KeyDown");
|
||||
suggestion.moveDown();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_UP:
|
||||
if (suggestion != null)
|
||||
if (suggestion.isVisible()) {
|
||||
//log("KeyUp");
|
||||
suggestion.moveUp();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.VK_BACK_SPACE:
|
||||
Messages.log("BK Key");
|
||||
break;
|
||||
case KeyEvent.VK_SPACE:
|
||||
if (suggestion != null) {
|
||||
if (suggestion.isVisible()) {
|
||||
Messages.log("Space bar, hide completion list");
|
||||
suggestion.setInvisible();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
super.processKeyEvent(evt);
|
||||
|
||||
// code completion disabled if Java tabs present
|
||||
if (!getJavaEditor().hasJavaTabs()) {
|
||||
if (evt.getID() == KeyEvent.KEY_TYPED) {
|
||||
processCompletionKeys(evt);
|
||||
} else if (!Platform.isMacOS() && evt.getID() == KeyEvent.KEY_RELEASED) {
|
||||
processCompletionKeys(evt);
|
||||
} else if (Platform.isMacOS() && evt.getID() == KeyEvent.KEY_RELEASED) {
|
||||
processControlSpace(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Special case for OS X, where Ctrl-Space is not detected as KEY_TYPED
|
||||
// https://github.com/processing/processing/issues/2699
|
||||
private void processControlSpace(final KeyEvent event) {
|
||||
if (event.getKeyCode() == KeyEvent.VK_SPACE && event.isControlDown()) {
|
||||
// Provide completions only if it's enabled
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
Messages.log("[KeyEvent]" + KeyEvent.getKeyText(event.getKeyCode()) + " |Prediction started");
|
||||
fetchPhrase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processCompletionKeys(final KeyEvent event) {
|
||||
char keyChar = event.getKeyChar();
|
||||
int keyCode = event.getKeyCode();
|
||||
if (keyChar == KeyEvent.VK_ENTER ||
|
||||
keyChar == KeyEvent.VK_ESCAPE ||
|
||||
keyChar == KeyEvent.VK_TAB ||
|
||||
(event.getID() == KeyEvent.KEY_RELEASED &&
|
||||
keyCode != KeyEvent.VK_LEFT && keyCode != KeyEvent.VK_RIGHT)) {
|
||||
// ignore
|
||||
} else if (keyChar == ')') {
|
||||
// https://github.com/processing/processing/issues/2741
|
||||
hideSuggestion();
|
||||
|
||||
} else if (keyChar == '.') {
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
Messages.log("[KeyEvent]" + KeyEvent.getKeyText(event.getKeyCode()) + " |Prediction started");
|
||||
fetchPhrase();
|
||||
}
|
||||
} else if (keyChar == ' ') { // Trigger on Ctrl-Space
|
||||
if (!Platform.isMacOS() && JavaMode.codeCompletionsEnabled &&
|
||||
(event.isControlDown() || event.isMetaDown())) {
|
||||
// Provide completions only if it's enabled
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
// Removed for https://github.com/processing/processing/issues/3847
|
||||
//try {
|
||||
// getDocument().remove(getCaretPosition() - 1, 1); // Remove the typed space
|
||||
Messages.log("[KeyEvent]" + event.getKeyChar() + " |Prediction started");
|
||||
fetchPhrase();
|
||||
//} catch (BadLocationException e) {
|
||||
// e.printStackTrace();
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
hideSuggestion(); // hide on spacebar
|
||||
}
|
||||
} else {
|
||||
if (JavaMode.codeCompletionsEnabled) {
|
||||
prepareSuggestions(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Kickstart auto-complete suggestions */
|
||||
private void prepareSuggestions(final KeyEvent evt) {
|
||||
// Provide completions only if it's enabled
|
||||
if (JavaMode.codeCompletionsEnabled &&
|
||||
(JavaMode.ccTriggerEnabled ||
|
||||
(suggestion != null && suggestion.isVisible()))) {
|
||||
Messages.log("[KeyEvent]" + evt.getKeyChar() + " |Prediction started");
|
||||
fetchPhrase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CompletionGenerator suggestionGenerator;
|
||||
|
||||
SwingWorker<Void, Void> suggestionWorker = null;
|
||||
|
||||
volatile boolean suggestionRunning = false;
|
||||
volatile boolean suggestionRequested = false;
|
||||
|
||||
/**
|
||||
* Retrieves the current word typed just before the caret.
|
||||
* Then triggers code completion for that word.
|
||||
* @param evt - the KeyEvent which triggered this method
|
||||
*/
|
||||
protected void fetchPhrase() {
|
||||
if (suggestionRunning) {
|
||||
suggestionRequested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
suggestionRunning = true;
|
||||
suggestionRequested = false;
|
||||
|
||||
final String text;
|
||||
final int caretLineIndex;
|
||||
final int caretLinePosition;
|
||||
{
|
||||
// Get caret position
|
||||
int caretPosition = getCaretPosition();
|
||||
if (caretPosition < 0) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get line index
|
||||
caretLineIndex = getCaretLine();
|
||||
if (caretLineIndex < 0) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get text of the line
|
||||
String lineText = getLineText(caretLineIndex);
|
||||
if (lineText == null) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get caret position on the line
|
||||
caretLinePosition = getCaretPosition() - getLineStartOffset(caretLineIndex);
|
||||
if (caretLinePosition <= 0) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get part of the line to the left of the caret
|
||||
if (caretLinePosition > lineText.length()) {
|
||||
suggestionRunning = false;
|
||||
return;
|
||||
}
|
||||
text = lineText.substring(0, caretLinePosition);
|
||||
}
|
||||
|
||||
// Adjust line number for tabbed sketches
|
||||
//int codeIndex = editor.getSketch().getCodeIndex(getJavaEditor().getCurrentTab());
|
||||
int codeIndex = editor.getSketch().getCurrentCodeIndex();
|
||||
int lineStartOffset = editor.getTextArea().getLineStartOffset(caretLineIndex);
|
||||
|
||||
getJavaEditor().getPreprocessingService().whenDone(ps -> {
|
||||
int lineNumber = ps.tabOffsetToJavaLine(codeIndex, lineStartOffset);
|
||||
|
||||
String phrase = null;
|
||||
DefaultListModel<CompletionCandidate> defListModel = null;
|
||||
|
||||
try {
|
||||
Messages.log("phrase parse start");
|
||||
phrase = parsePhrase(text);
|
||||
Messages.log("phrase: " + phrase);
|
||||
if (phrase != null) {
|
||||
List<CompletionCandidate> candidates;
|
||||
|
||||
candidates = suggestionGenerator.preparePredictions(ps, phrase, lineNumber);
|
||||
|
||||
if (!suggestionRequested) {
|
||||
|
||||
// // don't show completions when the outline is visible
|
||||
// boolean showSuggestions =
|
||||
// astGenerator.sketchOutline == null || !astGenerator.sketchOutline.isVisible();
|
||||
|
||||
// if (showSuggestions && phrase != null &&
|
||||
if (candidates != null && !candidates.isEmpty()) {
|
||||
Collections.sort(candidates);
|
||||
defListModel = CompletionGenerator.filterPredictions(candidates);
|
||||
Messages.log("Got: " + candidates.size() + " candidates, " + defListModel.size() + " filtered");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final String finalPhrase = phrase;
|
||||
final DefaultListModel<CompletionCandidate> finalDefListModel = defListModel;
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
|
||||
suggestionRunning = false;
|
||||
if (suggestionRequested) {
|
||||
Messages.log("completion invalidated");
|
||||
fetchPhrase();
|
||||
return;
|
||||
}
|
||||
|
||||
Messages.log("completion finishing");
|
||||
|
||||
if (finalDefListModel != null) {
|
||||
showSuggestion(finalDefListModel, finalPhrase);
|
||||
} else {
|
||||
hideSuggestion();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Messages.loge("error while preparing suggestions", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected static String parsePhrase(final String lineText) {
|
||||
boolean overloading = false;
|
||||
|
||||
{ // Check if we can provide suggestions for this phrase ending
|
||||
String trimmedLineText = lineText.trim();
|
||||
if (trimmedLineText.length() == 0) return null;
|
||||
|
||||
char lastChar = trimmedLineText.charAt(trimmedLineText.length() - 1);
|
||||
if (lastChar == '.') {
|
||||
trimmedLineText = trimmedLineText.substring(0, trimmedLineText.length() - 1).trim();
|
||||
if (trimmedLineText.length() == 0) return null;
|
||||
lastChar = trimmedLineText.charAt(trimmedLineText.length() - 1);
|
||||
switch (lastChar) {
|
||||
case ')':
|
||||
case ']':
|
||||
case '"':
|
||||
break; // We can suggest for these
|
||||
default:
|
||||
if (!Character.isJavaIdentifierPart(lastChar)) {
|
||||
return null; // Not something we can suggest
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (lastChar == '(') {
|
||||
overloading = true; // We can suggest overloaded methods
|
||||
} else if (!Character.isJavaIdentifierPart(lastChar)) {
|
||||
return null; // Not something we can suggest
|
||||
}
|
||||
}
|
||||
|
||||
final int currentCharIndex = lineText.length() - 1;
|
||||
|
||||
{ // Check if the caret is in the comment
|
||||
int commentStart = lineText.indexOf("//", 0);
|
||||
if (commentStart >= 0 && currentCharIndex > commentStart) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Index the line
|
||||
BitSet isInLiteral = new BitSet(lineText.length());
|
||||
BitSet isInBrackets = new BitSet(lineText.length());
|
||||
|
||||
{ // Mark parts in literals
|
||||
boolean inString = false;
|
||||
boolean inChar = false;
|
||||
boolean inEscaped = false;
|
||||
|
||||
for (int i = 0; i < lineText.length(); i++) {
|
||||
if (!inEscaped) {
|
||||
switch (lineText.charAt(i)) {
|
||||
case '\"':
|
||||
if (!inChar) inString = !inString;
|
||||
break;
|
||||
case '\'':
|
||||
if (!inString) inChar = !inChar;
|
||||
break;
|
||||
case '\\':
|
||||
if (inString || inChar) {
|
||||
inEscaped = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
inEscaped = false;
|
||||
}
|
||||
isInLiteral.set(i, inString || inChar);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInLiteral.get(currentCharIndex)) return null;
|
||||
|
||||
{ // Mark parts in top level brackets
|
||||
int depth = overloading ? 1 : 0;
|
||||
int bracketStart = overloading ? lineText.length() : 0;
|
||||
int squareDepth = 0;
|
||||
int squareBracketStart = 0;
|
||||
|
||||
bracketLoop: for (int i = lineText.length() - 1; i >= 0; i--) {
|
||||
if (!isInLiteral.get(i)) {
|
||||
switch (lineText.charAt(i)) {
|
||||
case ')':
|
||||
if (depth == 0) bracketStart = i;
|
||||
depth++;
|
||||
break;
|
||||
case '(':
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
isInBrackets.set(i, bracketStart);
|
||||
} else if (depth < 0) {
|
||||
break bracketLoop;
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
if (squareDepth == 0) squareBracketStart = i;
|
||||
squareDepth++;
|
||||
break;
|
||||
case '[':
|
||||
squareDepth--;
|
||||
if (squareDepth == 0) {
|
||||
isInBrackets.set(i, squareBracketStart);
|
||||
} else if (squareDepth < 0) {
|
||||
break bracketLoop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (depth > 0) isInBrackets.set(0, bracketStart);
|
||||
if (squareDepth > 0) isInBrackets.set(0, squareBracketStart);
|
||||
}
|
||||
|
||||
// Walk the line from the end while it makes sense
|
||||
int position = currentCharIndex;
|
||||
parseLoop: while (position >= 0) {
|
||||
int currChar = lineText.charAt(position);
|
||||
switch (currChar) {
|
||||
case '.': // Grab it
|
||||
position--;
|
||||
break;
|
||||
case '[':
|
||||
break parseLoop; // End of scope
|
||||
case ']': // Grab the whole region in square brackets
|
||||
position = isInBrackets.previousClearBit(position-1);
|
||||
break;
|
||||
case '(':
|
||||
if (isInBrackets.get(position)) {
|
||||
position--; // This checks for first bracket while overloading
|
||||
break;
|
||||
}
|
||||
break parseLoop; // End of scope
|
||||
case ')': // Grab the whole region in brackets
|
||||
position = isInBrackets.previousClearBit(position-1);
|
||||
break;
|
||||
case '"': // Grab the whole literal and quit
|
||||
position = isInLiteral.previousClearBit(position - 1);
|
||||
break parseLoop;
|
||||
default:
|
||||
if (Character.isJavaIdentifierPart(currChar)) {
|
||||
position--; // Grab the identifier
|
||||
} else if (Character.isWhitespace(currChar)) {
|
||||
position--; // Grab whitespace too
|
||||
} else {
|
||||
break parseLoop; // Got a char ending the phrase
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
position++;
|
||||
|
||||
// Extract phrase
|
||||
String phrase = lineText.substring(position, lineText.length()).trim();
|
||||
Messages.log(phrase);
|
||||
|
||||
if (phrase.length() == 0 || Character.isDigit(phrase.charAt(0))) {
|
||||
return null; // Can't suggest for numbers or empty phrases
|
||||
}
|
||||
return phrase;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates location of caret and displays the suggestion pop-up.
|
||||
*/
|
||||
protected void showSuggestion(DefaultListModel<CompletionCandidate> listModel, String subWord) {
|
||||
// TODO can this be ListModel instead? why is size() in DefaultListModel
|
||||
// different from getSize() in ListModel (or are they, really?)
|
||||
hideSuggestion();
|
||||
|
||||
if (listModel.size() != 0) {
|
||||
int position = getCaretPosition();
|
||||
try {
|
||||
Point location =
|
||||
new Point(offsetToX(getCaretLine(),
|
||||
position - getLineStartOffset(getCaretLine())),
|
||||
lineToY(getCaretLine()) + getPainter().getLineHeight());
|
||||
suggestion = new CompletionPanel(this, position, subWord,
|
||||
listModel, location, getJavaEditor());
|
||||
requestFocusInWindow();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
Messages.log("TextArea: No suggestions to show.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Hides suggestion popup */
|
||||
public void hideSuggestion() {
|
||||
if (suggestion != null) {
|
||||
suggestion.setInvisible();
|
||||
//log("Suggestion hidden.");
|
||||
suggestion = null; // TODO: check if we dispose the window properly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
// TWEAK MODE
|
||||
|
||||
|
||||
// save input listeners to stop/start text edit
|
||||
protected ComponentListener[] baseCompListeners;
|
||||
protected MouseListener[] baseMouseListeners;
|
||||
protected MouseMotionListener[] baseMotionListeners;
|
||||
protected KeyListener[] baseKeyListeners;
|
||||
protected boolean tweakMode;
|
||||
|
||||
|
||||
/* remove all standard interaction listeners */
|
||||
public void tweakRemoveListeners() {
|
||||
if (baseCompListeners == null) {
|
||||
// First time in tweak mode, grab the default listeners. Moved from the
|
||||
// constructor since not all listeners may have been added at that point.
|
||||
baseCompListeners = painter.getComponentListeners();
|
||||
baseMouseListeners = painter.getMouseListeners();
|
||||
baseMotionListeners = painter.getMouseMotionListeners();
|
||||
baseKeyListeners = editor.getKeyListeners();
|
||||
}
|
||||
ComponentListener[] componentListeners = painter.getComponentListeners();
|
||||
MouseListener[] mouseListeners = painter.getMouseListeners();
|
||||
MouseMotionListener[] mouseMotionListeners = painter.getMouseMotionListeners();
|
||||
KeyListener[] keyListeners = editor.getKeyListeners();
|
||||
|
||||
for (ComponentListener cl : componentListeners) {
|
||||
painter.removeComponentListener(cl);
|
||||
}
|
||||
for (MouseListener ml : mouseListeners) {
|
||||
painter.removeMouseListener(ml);
|
||||
}
|
||||
for (MouseMotionListener mml : mouseMotionListeners) {
|
||||
painter.removeMouseMotionListener(mml);
|
||||
}
|
||||
for (KeyListener kl : keyListeners) {
|
||||
editor.removeKeyListener(kl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void startTweakMode() {
|
||||
// ignore if we are already in interactiveMode
|
||||
if (!tweakMode) {
|
||||
tweakRemoveListeners();
|
||||
getJavaPainter().startTweakMode();
|
||||
this.editable = false;
|
||||
this.caretBlinks = false;
|
||||
this.setCaretVisible(false);
|
||||
tweakMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void stopTweakMode() {
|
||||
// ignore if we are not in interactive mode
|
||||
if (tweakMode) {
|
||||
tweakRemoveListeners();
|
||||
tweakRestoreBaseListeners();
|
||||
getJavaPainter().stopTweakMode();
|
||||
editable = true;
|
||||
caretBlinks = true;
|
||||
setCaretVisible(true);
|
||||
tweakMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void tweakRestoreBaseListeners() {
|
||||
// add the original text-edit listeners
|
||||
for (ComponentListener cl : baseCompListeners) {
|
||||
painter.addComponentListener(cl);
|
||||
}
|
||||
for (MouseListener ml : baseMouseListeners) {
|
||||
painter.addMouseListener(ml);
|
||||
}
|
||||
for (MouseMotionListener mml : baseMotionListeners) {
|
||||
painter.addMouseMotionListener(mml);
|
||||
}
|
||||
for (KeyListener kl : baseKeyListeners) {
|
||||
editor.addKeyListener(kl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void updateInterface(List<List<Handle>> handles,
|
||||
List<List<ColorControlBox>> colorBoxes) {
|
||||
getJavaPainter().updateTweakInterface(handles, colorBoxes);
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-16 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
import processing.app.SketchCode;
|
||||
import processing.app.syntax.PdeTextAreaPainter;
|
||||
import processing.app.syntax.TextAreaDefaults;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.tweak.ColorControlBox;
|
||||
import processing.mode.java.tweak.ColorSelector;
|
||||
import processing.mode.java.tweak.Handle;
|
||||
import processing.mode.java.tweak.Settings;
|
||||
|
||||
|
||||
/**
|
||||
* Customized line painter to handle tweak mode features.
|
||||
*/
|
||||
public class JavaTextAreaPainter extends PdeTextAreaPainter {
|
||||
|
||||
public JavaTextAreaPainter(final JavaTextArea textArea, TextAreaDefaults defaults) {
|
||||
super(textArea, defaults);
|
||||
|
||||
// TweakMode code
|
||||
tweakMode = false;
|
||||
cursorType = Cursor.DEFAULT_CURSOR;
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
// TWEAK MODE
|
||||
|
||||
|
||||
protected int horizontalAdjustment = 0;
|
||||
|
||||
public boolean tweakMode = false;
|
||||
public List<List<Handle>> handles;
|
||||
public List<List<ColorControlBox>> colorBoxes;
|
||||
|
||||
public Handle mouseHandle = null;
|
||||
public ColorSelector colorSelector;
|
||||
|
||||
int cursorType;
|
||||
BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||
Cursor blankCursor;
|
||||
// this is a temporary workaround for the CHIP, will be removed
|
||||
{
|
||||
Dimension cursorSize = Toolkit.getDefaultToolkit().getBestCursorSize(16, 16);
|
||||
if (cursorSize.width == 0 || cursorSize.height == 0) {
|
||||
blankCursor = Cursor.getDefaultCursor();
|
||||
} else {
|
||||
blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(0, 0), "blank cursor");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void paint(Graphics gfx) {
|
||||
super.paint(gfx);
|
||||
|
||||
if (tweakMode && handles != null) {
|
||||
int currentTab = getCurrentCodeIndex();
|
||||
// enable anti-aliasing
|
||||
Graphics2D g2d = (Graphics2D)gfx;
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
for (Handle n : handles.get(currentTab)) {
|
||||
// update n position and width, and draw it
|
||||
int lineStartChar = textArea.getLineStartOffset(n.line);
|
||||
int x = textArea.offsetToX(n.line, n.newStartChar - lineStartChar);
|
||||
int y = textArea.lineToY(n.line) + fm.getHeight() + 1;
|
||||
int end = textArea.offsetToX(n.line, n.newEndChar - lineStartChar);
|
||||
n.setPos(x, y);
|
||||
n.setWidth(end - x);
|
||||
n.draw(g2d, n==mouseHandle);
|
||||
}
|
||||
|
||||
// draw color boxes
|
||||
for (ColorControlBox cBox: colorBoxes.get(currentTab)) {
|
||||
int lineStartChar = textArea.getLineStartOffset(cBox.getLine());
|
||||
int x = textArea.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar);
|
||||
int y = textArea.lineToY(cBox.getLine()) + fm.getDescent();
|
||||
cBox.setPos(x, y+1);
|
||||
cBox.draw(g2d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void startTweakMode() {
|
||||
addMouseListener(new MouseListener() {
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (mouseHandle != null) {
|
||||
mouseHandle.resetProgress();
|
||||
mouseHandle = null;
|
||||
|
||||
updateCursor(e.getX(), e.getY());
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
int currentTab = getCurrentCodeIndex();
|
||||
// check for clicks on number handles
|
||||
for (Handle n : handles.get(currentTab)) {
|
||||
if (n.pick(e.getX(), e.getY())) {
|
||||
cursorType = -1;
|
||||
JavaTextAreaPainter.this.setCursor(blankCursor);
|
||||
mouseHandle = n;
|
||||
mouseHandle.setCenterX(e.getX());
|
||||
repaint();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check for clicks on color boxes
|
||||
for (ColorControlBox box : colorBoxes.get(currentTab)) {
|
||||
if (box.pick(e.getX(), e.getY())) {
|
||||
if (colorSelector != null) {
|
||||
// we already show a color selector, close it
|
||||
colorSelector.frame.dispatchEvent(new WindowEvent(colorSelector.frame, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
|
||||
colorSelector = new ColorSelector(box);
|
||||
colorSelector.frame.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosing(WindowEvent e) {
|
||||
colorSelector.frame.setVisible(false);
|
||||
colorSelector = null;
|
||||
}
|
||||
});
|
||||
colorSelector.show(getLocationOnScreen().x + e.getX() + 30,
|
||||
getLocationOnScreen().y + e.getY() - 130);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) { }
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) { }
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) { }
|
||||
});
|
||||
|
||||
addMouseMotionListener(new MouseMotionListener() {
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
updateCursor(e.getX(), e.getY());
|
||||
|
||||
if (!Settings.alwaysShowColorBoxes) {
|
||||
showHideColorBoxes(e.getY());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
if (mouseHandle != null) {
|
||||
// set the current drag amount of the arrows
|
||||
mouseHandle.setCurrentX(e.getX());
|
||||
|
||||
// update code text with the new value
|
||||
updateCodeText();
|
||||
|
||||
if (colorSelector != null) {
|
||||
colorSelector.refreshColor();
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
tweakMode = true;
|
||||
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
protected void stopTweakMode() {
|
||||
tweakMode = false;
|
||||
|
||||
if (colorSelector != null) {
|
||||
colorSelector.hide();
|
||||
WindowEvent windowEvent =
|
||||
new WindowEvent(colorSelector.frame, WindowEvent.WINDOW_CLOSING);
|
||||
colorSelector.frame.dispatchEvent(windowEvent);
|
||||
}
|
||||
|
||||
setCursor(new Cursor(Cursor.TEXT_CURSOR));
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
protected void updateTweakInterface(List<List<Handle>> handles,
|
||||
List<List<ColorControlBox>> colorBoxes) {
|
||||
this.handles = handles;
|
||||
this.colorBoxes = colorBoxes;
|
||||
|
||||
updateTweakInterfacePositions();
|
||||
repaint();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize all the number changing interfaces.
|
||||
* synchronize this method to prevent the execution of 'paint' in the middle.
|
||||
* (don't paint while we make changes to the text of the editor)
|
||||
*/
|
||||
private synchronized void updateTweakInterfacePositions() {
|
||||
SketchCode[] code = getEditor().getSketch().getCode();
|
||||
int prevScroll = textArea.getVerticalScrollPosition();
|
||||
String prevText = textArea.getText();
|
||||
|
||||
for (int tab=0; tab<code.length; tab++) {
|
||||
String tabCode = getJavaEditor().baseCode[tab];
|
||||
textArea.setText(tabCode);
|
||||
for (Handle n : handles.get(tab)) {
|
||||
int lineStartChar = textArea.getLineStartOffset(n.line);
|
||||
int x = textArea.offsetToX(n.line, n.newStartChar - lineStartChar);
|
||||
int end = textArea.offsetToX(n.line, n.newEndChar - lineStartChar);
|
||||
int y = textArea.lineToY(n.line) + fm.getHeight() + 1;
|
||||
n.initInterface(x, y, end-x, fm.getHeight());
|
||||
}
|
||||
|
||||
for (ColorControlBox cBox : colorBoxes.get(tab)) {
|
||||
int lineStartChar = textArea.getLineStartOffset(cBox.getLine());
|
||||
int x = textArea.offsetToX(cBox.getLine(), cBox.getCharIndex() - lineStartChar);
|
||||
int y = textArea.lineToY(cBox.getLine()) + fm.getDescent();
|
||||
cBox.initInterface(this, x, y+1, fm.getHeight()-2, fm.getHeight()-2);
|
||||
}
|
||||
}
|
||||
|
||||
textArea.setText(prevText);
|
||||
textArea.scrollTo(prevScroll, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Take the saved code of the current tab and replace
|
||||
* all numbers with their current values.
|
||||
* Update TextArea with the new code.
|
||||
*/
|
||||
public void updateCodeText() {
|
||||
int charInc = 0;
|
||||
int currentTab = getCurrentCodeIndex();
|
||||
SketchCode sc = getEditor().getSketch().getCode(currentTab);
|
||||
String code = getJavaEditor().baseCode[currentTab];
|
||||
|
||||
for (Handle n : handles.get(currentTab)) {
|
||||
int s = n.startChar + charInc;
|
||||
int e = n.endChar + charInc;
|
||||
code = replaceString(code, s, e, n.strNewValue);
|
||||
n.newStartChar = n.startChar + charInc;
|
||||
charInc += n.strNewValue.length() - n.strValue.length();
|
||||
n.newEndChar = n.endChar + charInc;
|
||||
}
|
||||
|
||||
replaceTextAreaCode(code);
|
||||
// update also the sketch code for later
|
||||
sc.setProgram(code);
|
||||
}
|
||||
|
||||
|
||||
// don't paint while we do the stuff below
|
||||
private synchronized void replaceTextAreaCode(String code) {
|
||||
// by default setText will scroll all the way to the end
|
||||
// remember current scroll position
|
||||
int scrollLine = textArea.getVerticalScrollPosition();
|
||||
int scrollHor = textArea.getHorizontalScrollPosition();
|
||||
textArea.setText(code);
|
||||
textArea.setOrigin(scrollLine, -scrollHor);
|
||||
}
|
||||
|
||||
|
||||
static private String replaceString(String str, int start, int end, String put) {
|
||||
return str.substring(0, start) + put + str.substring(end, str.length());
|
||||
}
|
||||
|
||||
|
||||
private void updateCursor(int mouseX, int mouseY) {
|
||||
int currentTab = getCurrentCodeIndex();
|
||||
for (Handle n : handles.get(currentTab)) {
|
||||
if (n.pick(mouseX, mouseY)) {
|
||||
cursorType = Cursor.W_RESIZE_CURSOR;
|
||||
setCursor(new Cursor(cursorType));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (ColorControlBox colorBox : colorBoxes.get(currentTab)) {
|
||||
if (colorBox.pick(mouseX, mouseY)) {
|
||||
cursorType = Cursor.HAND_CURSOR;
|
||||
setCursor(new Cursor(cursorType));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursorType == Cursor.W_RESIZE_CURSOR ||
|
||||
cursorType == Cursor.HAND_CURSOR ||
|
||||
cursorType == -1) {
|
||||
cursorType = Cursor.DEFAULT_CURSOR;
|
||||
setCursor(new Cursor(cursorType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void showHideColorBoxes(int y) {
|
||||
// display the box if the mouse if in the same line.
|
||||
// always keep the color box of the color selector.
|
||||
int currentTab = getCurrentCodeIndex();
|
||||
|
||||
boolean change = false;
|
||||
for (ColorControlBox box : colorBoxes.get(currentTab)) {
|
||||
if (box.setMouseY(y)) {
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (colorSelector != null) {
|
||||
colorSelector.colorBox.visible = true;
|
||||
}
|
||||
|
||||
if (change) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
private JavaEditor getJavaEditor() {
|
||||
return (JavaEditor) getEditor();
|
||||
}
|
||||
|
||||
|
||||
private int getCurrentCodeIndex() {
|
||||
return getEditor().getSketch().getCurrentCodeIndex();
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.Document;
|
||||
|
||||
import processing.app.SketchCode;
|
||||
import processing.mode.java.JavaEditor;
|
||||
|
||||
|
||||
public class PDEX {
|
||||
static private final boolean SHOW_DEBUG_TREE = false;
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private ErrorChecker errorChecker;
|
||||
|
||||
private InspectMode inspect;
|
||||
private ShowUsage usage;
|
||||
private Rename rename;
|
||||
private DebugTree debugTree;
|
||||
|
||||
private PreprocessingService pps;
|
||||
|
||||
|
||||
public PDEX(JavaEditor editor, PreprocessingService pps) {
|
||||
this.pps = pps;
|
||||
|
||||
this.enabled = !editor.hasJavaTabs();
|
||||
|
||||
errorChecker = new ErrorChecker(editor, pps);
|
||||
|
||||
usage = new ShowUsage(editor, pps);
|
||||
inspect = new InspectMode(editor, pps, usage);
|
||||
rename = new Rename(editor, pps, usage);
|
||||
|
||||
if (SHOW_DEBUG_TREE) {
|
||||
debugTree = new DebugTree(editor, pps);
|
||||
}
|
||||
|
||||
for (SketchCode code : editor.getSketch().getCode()) {
|
||||
Document document = code.getDocument();
|
||||
addDocumentListener(document);
|
||||
}
|
||||
|
||||
sketchChanged();
|
||||
}
|
||||
|
||||
|
||||
public void addDocumentListener(Document doc) {
|
||||
if (doc != null) doc.addDocumentListener(sketchChangedListener);
|
||||
}
|
||||
|
||||
|
||||
final DocumentListener sketchChangedListener = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
sketchChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
sketchChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
sketchChanged();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void sketchChanged() {
|
||||
errorChecker.notifySketchChanged();
|
||||
pps.notifySketchChanged();
|
||||
}
|
||||
|
||||
|
||||
public void preferencesChanged() {
|
||||
errorChecker.preferencesChanged();
|
||||
sketchChanged();
|
||||
}
|
||||
|
||||
|
||||
public void hasJavaTabsChanged(boolean hasJavaTabs) {
|
||||
enabled = !hasJavaTabs;
|
||||
if (!enabled) {
|
||||
usage.hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
inspect.dispose();
|
||||
errorChecker.dispose();
|
||||
usage.dispose();
|
||||
rename.dispose();
|
||||
if (debugTree != null) {
|
||||
debugTree.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void documentChanged(Document newDoc) {
|
||||
addDocumentListener(newDoc);
|
||||
}
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import com.google.classpath.ClassPath;
|
||||
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import processing.app.Sketch;
|
||||
import processing.core.PApplet;
|
||||
import processing.mode.java.pdex.TextTransform.OffsetMapper;
|
||||
|
||||
public class PreprocessedSketch {
|
||||
|
||||
public final Sketch sketch;
|
||||
|
||||
public final CompilationUnit compilationUnit;
|
||||
|
||||
public final String[] classPathArray;
|
||||
public final ClassPath classPath;
|
||||
public final URLClassLoader classLoader;
|
||||
|
||||
public final String[] searchClassPathArray;
|
||||
|
||||
public final int[] tabStartOffsets;
|
||||
|
||||
public final String scrubbedPdeCode;
|
||||
public final String pdeCode;
|
||||
public final String javaCode;
|
||||
|
||||
public final OffsetMapper offsetMapper;
|
||||
|
||||
public final boolean hasSyntaxErrors;
|
||||
public final boolean hasCompilationErrors;
|
||||
|
||||
public final List<ImportStatement> programImports;
|
||||
public final List<ImportStatement> coreAndDefaultImports;
|
||||
public final List<ImportStatement> codeFolderImports;
|
||||
|
||||
|
||||
|
||||
/// JAVA -> SKETCH -----------------------------------------------------------
|
||||
|
||||
|
||||
public static class SketchInterval {
|
||||
|
||||
public static final SketchInterval BEFORE_START = new SketchInterval(-1, -1, -1, -1, -1);
|
||||
|
||||
private SketchInterval(int tabIndex,
|
||||
int startTabOffset, int stopTabOffset,
|
||||
int startPdeOffset, int stopPdeOffset) {
|
||||
this.tabIndex = tabIndex;
|
||||
this.startTabOffset = startTabOffset;
|
||||
this.stopTabOffset = stopTabOffset;
|
||||
this.startPdeOffset = startPdeOffset;
|
||||
this.stopPdeOffset = stopPdeOffset;
|
||||
}
|
||||
|
||||
final int tabIndex;
|
||||
final int startTabOffset;
|
||||
final int stopTabOffset;
|
||||
|
||||
final int startPdeOffset;
|
||||
final int stopPdeOffset;
|
||||
}
|
||||
|
||||
|
||||
public boolean inRange(SketchInterval interval) {
|
||||
return interval != SketchInterval.BEFORE_START &&
|
||||
interval.stopPdeOffset < pdeCode.length();
|
||||
}
|
||||
|
||||
|
||||
public String getPdeCode(SketchInterval si) {
|
||||
if (si == SketchInterval.BEFORE_START) return "";
|
||||
int stop = Math.min(si.stopPdeOffset, pdeCode.length());
|
||||
int start = Math.min(si.startPdeOffset, stop);
|
||||
return pdeCode.substring(start, stop);
|
||||
}
|
||||
|
||||
|
||||
public SketchInterval mapJavaToSketch(ASTNode node) {
|
||||
return mapJavaToSketch(node.getStartPosition(),
|
||||
node.getStartPosition() + node.getLength());
|
||||
}
|
||||
|
||||
|
||||
public SketchInterval mapJavaToSketch(IProblem iproblem) {
|
||||
return mapJavaToSketch(iproblem.getSourceStart(),
|
||||
iproblem.getSourceEnd() + 1); // make it exclusive
|
||||
}
|
||||
|
||||
|
||||
public SketchInterval mapJavaToSketch(int startJavaOffset, int stopJavaOffset) {
|
||||
int length = stopJavaOffset - startJavaOffset;
|
||||
int startPdeOffset = javaOffsetToPdeOffset(startJavaOffset);
|
||||
int stopPdeOffset;
|
||||
if (length == 0) {
|
||||
stopPdeOffset = startPdeOffset;
|
||||
} else {
|
||||
stopPdeOffset = javaOffsetToPdeOffset(stopJavaOffset-1);
|
||||
if (stopPdeOffset >= 0 && (stopPdeOffset > startPdeOffset || length == 1)) {
|
||||
stopPdeOffset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (startPdeOffset < 0 || stopPdeOffset < 0) {
|
||||
return SketchInterval.BEFORE_START;
|
||||
}
|
||||
|
||||
int tabIndex = pdeOffsetToTabIndex(startPdeOffset);
|
||||
|
||||
if (startPdeOffset >= pdeCode.length()) {
|
||||
startPdeOffset = pdeCode.length() - 1;
|
||||
stopPdeOffset = startPdeOffset + 1;
|
||||
}
|
||||
|
||||
return new SketchInterval(tabIndex,
|
||||
pdeOffsetToTabOffset(tabIndex, startPdeOffset),
|
||||
pdeOffsetToTabOffset(tabIndex, stopPdeOffset),
|
||||
startPdeOffset, stopPdeOffset);
|
||||
}
|
||||
|
||||
|
||||
private int javaOffsetToPdeOffset(int javaOffset) {
|
||||
return offsetMapper.getInputOffset(javaOffset);
|
||||
}
|
||||
|
||||
|
||||
public int pdeOffsetToTabIndex(int pdeOffset) {
|
||||
pdeOffset = Math.max(0, pdeOffset);
|
||||
int tab = Arrays.binarySearch(tabStartOffsets, pdeOffset);
|
||||
if (tab < 0) {
|
||||
tab = -(tab + 1) - 1;
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
|
||||
|
||||
public int pdeOffsetToTabOffset(int tabIndex, int pdeOffset) {
|
||||
int tabStartOffset = tabStartOffsets[clipTabIndex(tabIndex)];
|
||||
return pdeOffset - tabStartOffset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// SKETCH -> JAVA -----------------------------------------------------------
|
||||
|
||||
|
||||
public int tabOffsetToJavaOffset(int tabIndex, int tabOffset) {
|
||||
int tabStartOffset = tabStartOffsets[clipTabIndex(tabIndex)];
|
||||
int pdeOffset = tabStartOffset + tabOffset;
|
||||
return offsetMapper.getOutputOffset(pdeOffset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// LINE NUMBERS -------------------------------------------------------------
|
||||
|
||||
|
||||
public int tabOffsetToJavaLine(int tabIndex, int tabOffset) {
|
||||
int javaOffset = tabOffsetToJavaOffset(tabIndex, tabOffset);
|
||||
return offsetToLine(javaCode, javaOffset);
|
||||
}
|
||||
|
||||
|
||||
public int tabOffsetToTabLine(int tabIndex, int tabOffset) {
|
||||
int tabStartOffset = tabStartOffsets[clipTabIndex(tabIndex)];
|
||||
return offsetToLine(pdeCode, tabStartOffset, tabStartOffset + tabOffset);
|
||||
}
|
||||
|
||||
|
||||
// TODO: optimize
|
||||
private static int offsetToLine(String text, int offset) {
|
||||
return offsetToLine(text, 0, offset);
|
||||
}
|
||||
|
||||
|
||||
// TODO: optimize
|
||||
private static int offsetToLine(String text, int start, int offset) {
|
||||
int line = 0;
|
||||
while (offset >= start) {
|
||||
offset = text.lastIndexOf('\n', offset-1);
|
||||
line++;
|
||||
}
|
||||
return line - 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Util ---------------------------------------------------------------------
|
||||
|
||||
private int clipTabIndex(int tabIndex) {
|
||||
return PApplet.constrain(tabIndex, 0, tabStartOffsets.length - 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// BUILDER BUSINESS /////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* There is a lot of fields and having constructor with this many parameters
|
||||
* is just not practical. Fill stuff into builder and then simply build it.
|
||||
* Builder also guards against calling methods in the middle of building process.
|
||||
*/
|
||||
|
||||
public static class Builder {
|
||||
public Sketch sketch;
|
||||
|
||||
public CompilationUnit compilationUnit;
|
||||
|
||||
public String[] classPathArray;
|
||||
public ClassPath classPath;
|
||||
public URLClassLoader classLoader;
|
||||
|
||||
public String[] searchClassPathArray;
|
||||
|
||||
public int[] tabStartOffsets = new int[0];
|
||||
|
||||
public String scrubbedPdeCode;
|
||||
public String pdeCode;
|
||||
public String javaCode;
|
||||
|
||||
public OffsetMapper offsetMapper;
|
||||
|
||||
public boolean hasSyntaxErrors;
|
||||
public boolean hasCompilationErrors;
|
||||
|
||||
public final List<ImportStatement> programImports = new ArrayList<>();
|
||||
public final List<ImportStatement> coreAndDefaultImports = new ArrayList<>();
|
||||
public final List<ImportStatement> codeFolderImports = new ArrayList<>();
|
||||
|
||||
public PreprocessedSketch build() {
|
||||
return new PreprocessedSketch(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static PreprocessedSketch empty() {
|
||||
return new Builder().build();
|
||||
}
|
||||
|
||||
private PreprocessedSketch(Builder b) {
|
||||
sketch = b.sketch;
|
||||
|
||||
compilationUnit = b.compilationUnit;
|
||||
|
||||
classPathArray = b.classPathArray;
|
||||
classPath = b.classPath;
|
||||
classLoader = b.classLoader;
|
||||
|
||||
searchClassPathArray = b.searchClassPathArray;
|
||||
|
||||
tabStartOffsets = b.tabStartOffsets;
|
||||
|
||||
scrubbedPdeCode = b.scrubbedPdeCode;
|
||||
pdeCode = b.pdeCode;
|
||||
javaCode = b.javaCode;
|
||||
|
||||
offsetMapper = b.offsetMapper != null ? b.offsetMapper : OffsetMapper.EMPTY_MAPPER;
|
||||
|
||||
hasSyntaxErrors = b.hasSyntaxErrors;
|
||||
hasCompilationErrors = b.hasCompilationErrors;
|
||||
|
||||
programImports = Collections.unmodifiableList(b.programImports);
|
||||
coreAndDefaultImports = Collections.unmodifiableList(b.coreAndDefaultImports);
|
||||
codeFolderImports = Collections.unmodifiableList(b.codeFolderImports);
|
||||
}
|
||||
}
|
||||
+725
@@ -0,0 +1,725 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import com.google.classpath.ClassPathFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.core.compiler.IProblem;
|
||||
import org.eclipse.jdt.core.dom.AST;
|
||||
import org.eclipse.jdt.core.dom.ASTParser;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
|
||||
import processing.app.Library;
|
||||
import processing.app.Messages;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.SketchCode;
|
||||
import processing.app.SketchException;
|
||||
import processing.app.Util;
|
||||
import processing.data.IntList;
|
||||
import processing.data.StringList;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.JavaMode;
|
||||
import processing.mode.java.pdex.TextTransform.OffsetMapper;
|
||||
import processing.mode.java.preproc.PdePreprocessor;
|
||||
import processing.mode.java.preproc.PdePreprocessor.Mode;
|
||||
|
||||
|
||||
/**
|
||||
* The main error checking service
|
||||
*/
|
||||
public class PreprocessingService {
|
||||
|
||||
protected final JavaEditor editor;
|
||||
|
||||
protected final ASTParser parser = ASTParser.newParser(AST.JLS8);
|
||||
|
||||
private final ClassPathFactory classPathFactory = new ClassPathFactory();
|
||||
|
||||
private final Thread preprocessingThread;
|
||||
private final BlockingQueue<Boolean> requestQueue = new ArrayBlockingQueue<>(1);
|
||||
|
||||
private final Object requestLock = new Object();
|
||||
|
||||
private final AtomicBoolean codeFolderChanged = new AtomicBoolean(true);
|
||||
private final AtomicBoolean librariesChanged = new AtomicBoolean(true);
|
||||
|
||||
private volatile boolean running;
|
||||
private CompletableFuture<PreprocessedSketch> preprocessingTask = new CompletableFuture<>();
|
||||
|
||||
private CompletableFuture<?> lastCallback =
|
||||
new CompletableFuture<Object>() {{
|
||||
complete(null); // initialization block
|
||||
}};
|
||||
|
||||
private volatile boolean isEnabled = true;
|
||||
|
||||
|
||||
public PreprocessingService(JavaEditor editor) {
|
||||
this.editor = editor;
|
||||
isEnabled = !editor.hasJavaTabs();
|
||||
|
||||
// Register listeners for first run
|
||||
whenDone(this::fireListeners);
|
||||
|
||||
preprocessingThread = new Thread(this::mainLoop, "ECS");
|
||||
preprocessingThread.start();
|
||||
}
|
||||
|
||||
|
||||
private void mainLoop() {
|
||||
running = true;
|
||||
PreprocessedSketch prevResult = null;
|
||||
CompletableFuture<?> runningCallbacks = null;
|
||||
Messages.log("PPS: Hi!");
|
||||
while (running) {
|
||||
try {
|
||||
try {
|
||||
requestQueue.take(); // blocking until requested
|
||||
} catch (InterruptedException e) {
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
|
||||
Messages.log("PPS: Starting");
|
||||
|
||||
prevResult = preprocessSketch(prevResult);
|
||||
|
||||
// Wait until callbacks finish before firing new wave
|
||||
// If new request arrives while waiting, break out and start preprocessing
|
||||
while (requestQueue.isEmpty() && runningCallbacks != null) {
|
||||
try {
|
||||
runningCallbacks.get(10, TimeUnit.MILLISECONDS);
|
||||
runningCallbacks = null;
|
||||
} catch (TimeoutException e) { }
|
||||
}
|
||||
|
||||
synchronized (requestLock) {
|
||||
if (requestQueue.isEmpty()) {
|
||||
runningCallbacks = lastCallback;
|
||||
Messages.log("PPS: Done");
|
||||
preprocessingTask.complete(prevResult);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Messages.loge("problem in preprocessor service loop", e);
|
||||
}
|
||||
}
|
||||
Messages.log("PPS: Bye!");
|
||||
}
|
||||
|
||||
|
||||
public void dispose() {
|
||||
cancel();
|
||||
running = false;
|
||||
preprocessingThread.interrupt();
|
||||
}
|
||||
|
||||
|
||||
public void cancel() {
|
||||
requestQueue.clear();
|
||||
}
|
||||
|
||||
|
||||
public void notifySketchChanged() {
|
||||
if (!isEnabled) return;
|
||||
synchronized (requestLock) {
|
||||
if (preprocessingTask.isDone()) {
|
||||
preprocessingTask = new CompletableFuture<>();
|
||||
// Register callback which executes all listeners
|
||||
whenDone(this::fireListeners);
|
||||
}
|
||||
requestQueue.offer(Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void notifyLibrariesChanged() {
|
||||
Messages.log("PPS: notified libraries changed");
|
||||
librariesChanged.set(true);
|
||||
notifySketchChanged();
|
||||
}
|
||||
|
||||
|
||||
public void notifyCodeFolderChanged() {
|
||||
Messages.log("PPS: snotified code folder changed");
|
||||
codeFolderChanged.set(true);
|
||||
notifySketchChanged();
|
||||
}
|
||||
|
||||
|
||||
private CompletableFuture<?> registerCallback(Consumer<PreprocessedSketch> callback) {
|
||||
synchronized (requestLock) {
|
||||
lastCallback = preprocessingTask
|
||||
// Run callback after both preprocessing task and previous callback
|
||||
.thenAcceptBothAsync(lastCallback, (ps, a) -> callback.accept(ps))
|
||||
// Make sure exception in callback won't cancel whole callback chain
|
||||
.handleAsync((res, e) -> {
|
||||
if (e != null) Messages.loge("PPS: exception in callback", e);
|
||||
return res;
|
||||
});
|
||||
return lastCallback;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void whenDone(Consumer<PreprocessedSketch> callback) {
|
||||
if (!isEnabled) return;
|
||||
registerCallback(callback);
|
||||
}
|
||||
|
||||
|
||||
public void whenDoneBlocking(Consumer<PreprocessedSketch> callback) {
|
||||
if (!isEnabled) return;
|
||||
try {
|
||||
registerCallback(callback).get(3000, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// LISTENERS ----------------------------------------------------------------
|
||||
|
||||
|
||||
private Set<Consumer<PreprocessedSketch>> listeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
|
||||
public void registerListener(Consumer<PreprocessedSketch> listener) {
|
||||
if (listener != null) listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
public void unregisterListener(Consumer<PreprocessedSketch> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
private void fireListeners(PreprocessedSketch ps) {
|
||||
for (Consumer<PreprocessedSketch> listener : listeners) {
|
||||
try {
|
||||
listener.accept(ps);
|
||||
} catch (Exception e) {
|
||||
Messages.loge("error when firing preprocessing listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
|
||||
|
||||
private PreprocessedSketch preprocessSketch(PreprocessedSketch prevResult) {
|
||||
|
||||
boolean firstCheck = prevResult == null;
|
||||
|
||||
PreprocessedSketch.Builder result = new PreprocessedSketch.Builder();
|
||||
|
||||
List<ImportStatement> codeFolderImports = result.codeFolderImports;
|
||||
List<ImportStatement> programImports = result.programImports;
|
||||
|
||||
JavaMode javaMode = (JavaMode) editor.getMode();
|
||||
Sketch sketch = result.sketch = editor.getSketch();
|
||||
String className = sketch.getName();
|
||||
|
||||
StringBuilder workBuffer = new StringBuilder();
|
||||
|
||||
// Combine code into one buffer
|
||||
IntList tabStartsList = new IntList();
|
||||
for (SketchCode sc : sketch.getCode()) {
|
||||
if (sc.isExtension("pde")) {
|
||||
tabStartsList.append(workBuffer.length());
|
||||
if (sc.getDocument() != null) {
|
||||
try {
|
||||
workBuffer.append(sc.getDocumentText());
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
workBuffer.append(sc.getProgram());
|
||||
}
|
||||
workBuffer.append('\n');
|
||||
}
|
||||
}
|
||||
result.tabStartOffsets = tabStartsList.array();
|
||||
|
||||
String pdeStage = result.pdeCode = workBuffer.toString();
|
||||
|
||||
|
||||
boolean reloadCodeFolder = firstCheck || codeFolderChanged.getAndSet(false);
|
||||
boolean reloadLibraries = firstCheck || librariesChanged.getAndSet(false);
|
||||
|
||||
// Core and default imports
|
||||
if (coreAndDefaultImports == null) {
|
||||
PdePreprocessor p = editor.createPreprocessor(null);
|
||||
coreAndDefaultImports = buildCoreAndDefaultImports(p);
|
||||
}
|
||||
result.coreAndDefaultImports.addAll(coreAndDefaultImports);
|
||||
|
||||
// Prepare code folder imports
|
||||
if (reloadCodeFolder) {
|
||||
codeFolderImports.addAll(buildCodeFolderImports(sketch));
|
||||
} else {
|
||||
codeFolderImports.addAll(prevResult.codeFolderImports);
|
||||
}
|
||||
|
||||
// TODO: convert unicode escapes to chars
|
||||
|
||||
SourceUtils.scrubCommentsAndStrings(workBuffer);
|
||||
|
||||
result.scrubbedPdeCode = workBuffer.toString();
|
||||
|
||||
Mode sketchMode = PdePreprocessor.parseMode(workBuffer);
|
||||
|
||||
// Prepare transforms to convert pde code into parsable code
|
||||
TextTransform toParsable = new TextTransform(pdeStage);
|
||||
toParsable.addAll(SourceUtils.insertImports(coreAndDefaultImports));
|
||||
toParsable.addAll(SourceUtils.insertImports(codeFolderImports));
|
||||
toParsable.addAll(SourceUtils.parseProgramImports(workBuffer, programImports));
|
||||
toParsable.addAll(SourceUtils.replaceTypeConstructors(workBuffer));
|
||||
toParsable.addAll(SourceUtils.replaceHexLiterals(workBuffer));
|
||||
toParsable.addAll(SourceUtils.wrapSketch(sketchMode, className, workBuffer.length()));
|
||||
|
||||
{ // Refresh sketch classloader and classpath if imports changed
|
||||
if (javaRuntimeClassPath == null) {
|
||||
javaRuntimeClassPath = buildJavaRuntimeClassPath();
|
||||
sketchModeClassPath = buildModeClassPath(javaMode, false);
|
||||
searchModeClassPath = buildModeClassPath(javaMode, true);
|
||||
}
|
||||
|
||||
if (reloadLibraries) {
|
||||
coreLibraryClassPath = buildCoreLibraryClassPath(javaMode);
|
||||
}
|
||||
|
||||
boolean rebuildLibraryClassPath = reloadLibraries ||
|
||||
checkIfImportsChanged(programImports, prevResult.programImports);
|
||||
|
||||
if (rebuildLibraryClassPath) {
|
||||
sketchLibraryClassPath = buildSketchLibraryClassPath(javaMode, programImports);
|
||||
searchLibraryClassPath = buildSearchLibraryClassPath(javaMode);
|
||||
}
|
||||
|
||||
boolean rebuildClassPath = reloadCodeFolder || rebuildLibraryClassPath ||
|
||||
prevResult.classLoader == null || prevResult.classPath == null ||
|
||||
prevResult.classPathArray == null || prevResult.searchClassPathArray == null;
|
||||
|
||||
if (reloadCodeFolder) {
|
||||
codeFolderClassPath = buildCodeFolderClassPath(sketch);
|
||||
}
|
||||
|
||||
if (rebuildClassPath) {
|
||||
{ // Sketch class path
|
||||
List<String> sketchClassPath = new ArrayList<>();
|
||||
sketchClassPath.addAll(javaRuntimeClassPath);
|
||||
sketchClassPath.addAll(sketchModeClassPath);
|
||||
sketchClassPath.addAll(sketchLibraryClassPath);
|
||||
sketchClassPath.addAll(coreLibraryClassPath);
|
||||
sketchClassPath.addAll(codeFolderClassPath);
|
||||
|
||||
String[] classPathArray = sketchClassPath.stream().toArray(String[]::new);
|
||||
URL[] urlArray = Arrays.stream(classPathArray)
|
||||
.map(path -> {
|
||||
try {
|
||||
return Paths.get(path).toUri().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
Messages.loge("malformed URL when preparing sketch classloader", e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(url -> url != null)
|
||||
.toArray(URL[]::new);
|
||||
result.classLoader = new URLClassLoader(urlArray, null);
|
||||
result.classPath = classPathFactory.createFromPaths(classPathArray);
|
||||
result.classPathArray = classPathArray;
|
||||
}
|
||||
|
||||
{ // Search class path
|
||||
List<String> searchClassPath = new ArrayList<>();
|
||||
searchClassPath.addAll(javaRuntimeClassPath);
|
||||
searchClassPath.addAll(searchModeClassPath);
|
||||
searchClassPath.addAll(searchLibraryClassPath);
|
||||
searchClassPath.addAll(coreLibraryClassPath);
|
||||
searchClassPath.addAll(codeFolderClassPath);
|
||||
|
||||
result.searchClassPathArray = searchClassPath.stream().toArray(String[]::new);
|
||||
}
|
||||
} else {
|
||||
result.classLoader = prevResult.classLoader;
|
||||
result.classPath = prevResult.classPath;
|
||||
result.searchClassPathArray = prevResult.searchClassPathArray;
|
||||
result.classPathArray = prevResult.classPathArray;
|
||||
}
|
||||
}
|
||||
|
||||
// Transform code to parsable state
|
||||
String parsableStage = toParsable.apply();
|
||||
OffsetMapper parsableMapper = toParsable.getMapper();
|
||||
|
||||
// Create intermediate AST for advanced preprocessing
|
||||
CompilationUnit parsableCU =
|
||||
makeAST(parser, parsableStage.toCharArray(), COMPILER_OPTIONS);
|
||||
|
||||
// Prepare advanced transforms which operate on AST
|
||||
TextTransform toCompilable = new TextTransform(parsableStage);
|
||||
toCompilable.addAll(SourceUtils.preprocessAST(parsableCU));
|
||||
|
||||
// Transform code to compilable state
|
||||
String compilableStage = toCompilable.apply();
|
||||
OffsetMapper compilableMapper = toCompilable.getMapper();
|
||||
char[] compilableStageChars = compilableStage.toCharArray();
|
||||
|
||||
// Create compilable AST to get syntax problems
|
||||
CompilationUnit compilableCU =
|
||||
makeAST(parser, compilableStageChars, COMPILER_OPTIONS);
|
||||
|
||||
// Get syntax problems from compilable AST
|
||||
result.hasSyntaxErrors |= Arrays.stream(compilableCU.getProblems())
|
||||
.anyMatch(IProblem::isError);
|
||||
|
||||
// Generate bindings after getting problems - avoids
|
||||
// 'missing type' errors when there are syntax problems
|
||||
CompilationUnit bindingsCU =
|
||||
makeASTWithBindings(parser, compilableStageChars, COMPILER_OPTIONS,
|
||||
className, result.classPathArray);
|
||||
|
||||
// Get compilation problems
|
||||
List<IProblem> bindingsProblems = Arrays.asList(bindingsCU.getProblems());
|
||||
result.hasCompilationErrors = bindingsProblems.stream()
|
||||
.anyMatch(IProblem::isError);
|
||||
|
||||
// Update builder
|
||||
result.offsetMapper = parsableMapper.thenMapping(compilableMapper);
|
||||
result.javaCode = compilableStage;
|
||||
result.compilationUnit = bindingsCU;
|
||||
|
||||
// Build it
|
||||
return result.build();
|
||||
}
|
||||
|
||||
|
||||
/// IMPORTS -----------------------------------------------------------------
|
||||
|
||||
private List<ImportStatement> coreAndDefaultImports;
|
||||
|
||||
|
||||
private static List<ImportStatement> buildCoreAndDefaultImports(PdePreprocessor p) {
|
||||
List<ImportStatement> result = new ArrayList<>();
|
||||
|
||||
for (String imp : p.getCoreImports()) {
|
||||
result.add(ImportStatement.parse(imp));
|
||||
}
|
||||
for (String imp : p.getDefaultImports()) {
|
||||
result.add(ImportStatement.parse(imp));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static List<ImportStatement> buildCodeFolderImports(Sketch sketch) {
|
||||
if (sketch.hasCodeFolder()) {
|
||||
File codeFolder = sketch.getCodeFolder();
|
||||
String codeFolderClassPath = Util.contentsToClassPath(codeFolder);
|
||||
StringList codeFolderPackages = Util.packageListFromClassPath(codeFolderClassPath);
|
||||
return StreamSupport.stream(codeFolderPackages.spliterator(), false)
|
||||
.map(ImportStatement::wholePackage)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
private static boolean checkIfImportsChanged(List<ImportStatement> prevImports,
|
||||
List<ImportStatement> imports) {
|
||||
if (imports.size() != prevImports.size()) {
|
||||
return true;
|
||||
} else {
|
||||
int count = imports.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!imports.get(i).isSameAs(prevImports.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// CLASSPATHS ---------------------------------------------------------------
|
||||
|
||||
|
||||
private List<String> javaRuntimeClassPath;
|
||||
|
||||
private List<String> sketchModeClassPath;
|
||||
private List<String> searchModeClassPath;
|
||||
|
||||
private List<String> coreLibraryClassPath;
|
||||
|
||||
private List<String> codeFolderClassPath;
|
||||
|
||||
private List<String> sketchLibraryClassPath;
|
||||
private List<String> searchLibraryClassPath;
|
||||
|
||||
|
||||
private static List<String> buildCodeFolderClassPath(Sketch sketch) {
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
|
||||
// Code folder
|
||||
if (sketch.hasCodeFolder()) {
|
||||
File codeFolder = sketch.getCodeFolder();
|
||||
String codeFolderClassPath = Util.contentsToClassPath(codeFolder);
|
||||
classPath.append(codeFolderClassPath);
|
||||
}
|
||||
|
||||
return sanitizeClassPath(classPath.toString());
|
||||
}
|
||||
|
||||
|
||||
private static List<String> buildModeClassPath(JavaMode mode, boolean search) {
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
|
||||
if (search) {
|
||||
String searchClassPath = mode.getSearchPath();
|
||||
if (searchClassPath != null) {
|
||||
classPath.append(File.pathSeparator).append(searchClassPath);
|
||||
}
|
||||
} else {
|
||||
Library coreLibrary = mode.getCoreLibrary();
|
||||
String coreClassPath = coreLibrary != null ?
|
||||
coreLibrary.getClassPath() : mode.getSearchPath();
|
||||
if (coreClassPath != null) {
|
||||
classPath.append(File.pathSeparator).append(coreClassPath);
|
||||
}
|
||||
}
|
||||
|
||||
return sanitizeClassPath(classPath.toString());
|
||||
}
|
||||
|
||||
|
||||
private static List<String> buildCoreLibraryClassPath(JavaMode mode) {
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
|
||||
for (Library lib : mode.coreLibraries) {
|
||||
classPath.append(File.pathSeparator).append(lib.getClassPath());
|
||||
}
|
||||
|
||||
return sanitizeClassPath(classPath.toString());
|
||||
}
|
||||
|
||||
|
||||
private static List<String> buildSearchLibraryClassPath(JavaMode mode) {
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
|
||||
for (Library lib : mode.contribLibraries) {
|
||||
classPath.append(File.pathSeparator).append(lib.getClassPath());
|
||||
}
|
||||
|
||||
return sanitizeClassPath(classPath.toString());
|
||||
}
|
||||
|
||||
|
||||
static private List<String> buildSketchLibraryClassPath(JavaMode mode,
|
||||
List<ImportStatement> programImports) {
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
|
||||
programImports.stream()
|
||||
.map(ImportStatement::getPackageName)
|
||||
.filter(pckg -> !ignorableImport(pckg))
|
||||
.map(pckg -> {
|
||||
try {
|
||||
return mode.getLibrary(pckg);
|
||||
} catch (SketchException e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(lib -> lib != null)
|
||||
.map(Library::getClassPath)
|
||||
.forEach(cp -> classPath.append(File.pathSeparator).append(cp));
|
||||
|
||||
return sanitizeClassPath(classPath.toString());
|
||||
}
|
||||
|
||||
|
||||
static private List<String> buildJavaRuntimeClassPath() {
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
|
||||
{ // Java runtime
|
||||
String rtPath = System.getProperty("java.home") +
|
||||
File.separator + "lib" + File.separator + "rt.jar";
|
||||
if (new File(rtPath).exists()) {
|
||||
classPath.append(File.pathSeparator).append(rtPath);
|
||||
} else {
|
||||
rtPath = System.getProperty("java.home") + File.separator + "jre" +
|
||||
File.separator + "lib" + File.separator + "rt.jar";
|
||||
if (new File(rtPath).exists()) {
|
||||
classPath.append(File.pathSeparator).append(rtPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // JavaFX runtime
|
||||
String jfxrtPath = System.getProperty("java.home") +
|
||||
File.separator + "lib" + File.separator + "ext" + File.separator + "jfxrt.jar";
|
||||
if (new File(jfxrtPath).exists()) {
|
||||
classPath.append(File.pathSeparator).append(jfxrtPath);
|
||||
} else {
|
||||
jfxrtPath = System.getProperty("java.home") + File.separator + "jre" +
|
||||
File.separator + "lib" + File.separator + "ext" + File.separator + "jfxrt.jar";
|
||||
if (new File(jfxrtPath).exists()) {
|
||||
classPath.append(File.pathSeparator).append(jfxrtPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sanitizeClassPath(classPath.toString());
|
||||
}
|
||||
|
||||
|
||||
private static List<String> sanitizeClassPath(String classPathString) {
|
||||
// Make sure class path does not contain empty string (home dir)
|
||||
return Arrays.stream(classPathString.split(File.pathSeparator))
|
||||
.filter(p -> p != null && !p.trim().isEmpty())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
private static CompilationUnit makeAST(ASTParser parser,
|
||||
char[] source,
|
||||
Map<String, String> options) {
|
||||
parser.setSource(source);
|
||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
parser.setCompilerOptions(options);
|
||||
parser.setStatementsRecovery(true);
|
||||
|
||||
return (CompilationUnit) parser.createAST(null);
|
||||
}
|
||||
|
||||
|
||||
private static CompilationUnit makeASTWithBindings(ASTParser parser,
|
||||
char[] source,
|
||||
Map<String, String> options,
|
||||
String className,
|
||||
String[] classPath) {
|
||||
parser.setSource(source);
|
||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
parser.setCompilerOptions(options);
|
||||
parser.setStatementsRecovery(true);
|
||||
parser.setUnitName(className);
|
||||
parser.setEnvironment(classPath, null, null, false);
|
||||
parser.setResolveBindings(true);
|
||||
|
||||
return (CompilationUnit) parser.createAST(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ignore processing packages, java.*.*. etc.
|
||||
*/
|
||||
static private boolean ignorableImport(String packageName) {
|
||||
return (packageName.startsWith("java.") ||
|
||||
packageName.startsWith("javax."));
|
||||
}
|
||||
|
||||
|
||||
static private final Map<String, String> COMPILER_OPTIONS;
|
||||
static {
|
||||
Map<String, String> compilerOptions = new HashMap<>();
|
||||
|
||||
JavaCore.setComplianceOptions(JavaCore.VERSION_1_7, compilerOptions);
|
||||
|
||||
// See http://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.jdt.doc.isv%2Fguide%2Fjdt_api_options.htm&anchor=compiler
|
||||
|
||||
final String[] generate = {
|
||||
JavaCore.COMPILER_LINE_NUMBER_ATTR,
|
||||
JavaCore.COMPILER_SOURCE_FILE_ATTR
|
||||
};
|
||||
|
||||
final String[] ignore = {
|
||||
JavaCore.COMPILER_PB_UNUSED_IMPORT,
|
||||
JavaCore.COMPILER_PB_MISSING_SERIAL_VERSION,
|
||||
JavaCore.COMPILER_PB_RAW_TYPE_REFERENCE,
|
||||
JavaCore.COMPILER_PB_REDUNDANT_TYPE_ARGUMENTS,
|
||||
JavaCore.COMPILER_PB_UNCHECKED_TYPE_OPERATION
|
||||
};
|
||||
|
||||
final String[] warn = {
|
||||
JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT,
|
||||
JavaCore.COMPILER_PB_NULL_REFERENCE,
|
||||
JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE,
|
||||
JavaCore.COMPILER_PB_REDUNDANT_NULL_CHECK,
|
||||
JavaCore.COMPILER_PB_POSSIBLE_ACCIDENTAL_BOOLEAN_ASSIGNMENT,
|
||||
JavaCore.COMPILER_PB_UNUSED_LABEL,
|
||||
JavaCore.COMPILER_PB_UNUSED_LOCAL,
|
||||
JavaCore.COMPILER_PB_UNUSED_OBJECT_ALLOCATION,
|
||||
JavaCore.COMPILER_PB_UNUSED_PARAMETER,
|
||||
JavaCore.COMPILER_PB_UNUSED_PRIVATE_MEMBER
|
||||
};
|
||||
|
||||
for (String s : generate) compilerOptions.put(s, JavaCore.GENERATE);
|
||||
for (String s : ignore) compilerOptions.put(s, JavaCore.IGNORE);
|
||||
for (String s : warn) compilerOptions.put(s, JavaCore.WARNING);
|
||||
|
||||
COMPILER_OPTIONS = Collections.unmodifiableMap(compilerOptions);
|
||||
}
|
||||
|
||||
|
||||
public void handleHasJavaTabsChange(boolean hasJavaTabs) {
|
||||
isEnabled = !hasJavaTabs;
|
||||
if (isEnabled) {
|
||||
notifySketchChanged();
|
||||
} else {
|
||||
preprocessingTask.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JRootPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
|
||||
import processing.app.Language;
|
||||
import processing.app.Platform;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.SketchCode;
|
||||
import processing.app.syntax.SyntaxDocument;
|
||||
import processing.app.ui.EditorStatus;
|
||||
import processing.app.ui.Toolkit;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
|
||||
import static processing.mode.java.pdex.ASTUtils.findAllOccurrences;
|
||||
import static processing.mode.java.pdex.ASTUtils.getSimpleNameAt;
|
||||
import static processing.mode.java.pdex.ASTUtils.resolveBinding;
|
||||
|
||||
|
||||
class Rename {
|
||||
final JavaEditor editor;
|
||||
final PreprocessingService pps;
|
||||
final ShowUsage showUsage;
|
||||
|
||||
final JDialog window;
|
||||
final JTextField textField;
|
||||
final JLabel oldNameLabel;
|
||||
|
||||
IBinding binding;
|
||||
PreprocessedSketch ps;
|
||||
|
||||
|
||||
Rename(JavaEditor editor, PreprocessingService pps, ShowUsage showUsage) {
|
||||
this.editor = editor;
|
||||
this.pps = pps;
|
||||
this.showUsage = showUsage;
|
||||
|
||||
// Add rename option
|
||||
JMenuItem renameItem = new JMenuItem(Language.text("editor.popup.rename"));
|
||||
renameItem.addActionListener(e -> handleRename());
|
||||
editor.getTextArea().getRightClickPopup().add(renameItem);
|
||||
|
||||
window = new JDialog(editor);
|
||||
JRootPane rootPane = window.getRootPane();
|
||||
window.setTitle("Rename");
|
||||
window.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
|
||||
Toolkit.registerWindowCloseKeys(rootPane, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
window.setVisible(false);
|
||||
}
|
||||
});
|
||||
Toolkit.setIcon(window);
|
||||
|
||||
window.setModal(true);
|
||||
window.setResizable(false);
|
||||
window.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
binding = null;
|
||||
ps = null;
|
||||
}
|
||||
});
|
||||
//window.setSize(Toolkit.zoom(250, 130));
|
||||
//window.setLayout(new BoxLayout(window.getContentPane(), BoxLayout.Y_AXIS));
|
||||
Box windowBox = Box.createVerticalBox();
|
||||
Toolkit.setBorder(windowBox);
|
||||
final int GAP = Toolkit.zoom(5);
|
||||
|
||||
{ // old name
|
||||
Box oldBox = Box.createHorizontalBox();
|
||||
oldNameLabel = new JLabel("Current Name: ");
|
||||
oldBox.add(oldNameLabel);
|
||||
//oldBox.add(Box.createHorizontalStrut(10));
|
||||
oldBox.add(Box.createHorizontalGlue());
|
||||
windowBox.add(oldBox);
|
||||
windowBox.add(Box.createVerticalStrut(GAP));
|
||||
}
|
||||
|
||||
{ // new name
|
||||
Box newBox = Box.createHorizontalBox();
|
||||
JLabel newNameLabel = new JLabel("New Name: ");
|
||||
newBox.add(newNameLabel);
|
||||
newBox.add(textField = new JTextField(20));
|
||||
newBox.add(Box.createHorizontalGlue());
|
||||
windowBox.add(newBox);
|
||||
windowBox.add(Box.createVerticalStrut(GAP*2));
|
||||
}
|
||||
|
||||
{ // button panel
|
||||
JButton showUsageButton = new JButton("Show Usage");
|
||||
showUsageButton.addActionListener(e -> {
|
||||
showUsage.findUsageAndUpdateTree(ps, binding);
|
||||
window.setVisible(false);
|
||||
});
|
||||
|
||||
JButton renameButton = new JButton("Rename");
|
||||
renameButton.addActionListener(e -> {
|
||||
final String newName = textField.getText().trim();
|
||||
if (!newName.isEmpty()) {
|
||||
if (newName.length() >= 1 &&
|
||||
newName.chars().limit(1).allMatch(Character::isUnicodeIdentifierStart) &&
|
||||
newName.chars().skip(1).allMatch(Character::isUnicodeIdentifierPart)) {
|
||||
rename(ps, binding, newName);
|
||||
window.setVisible(false);
|
||||
} else {
|
||||
String msg = String.format("'%s' is not a valid name", newName);
|
||||
JOptionPane.showMessageDialog(editor, msg, "Naming is Hard",
|
||||
JOptionPane.PLAIN_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
rootPane.setDefaultButton(renameButton);
|
||||
|
||||
//JPanel panelBottom = new JPanel();
|
||||
//panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS));
|
||||
Box buttonBox = Box.createHorizontalBox();
|
||||
//Toolkit.setBorder(panelBottom, 5, 5, 5, 5);
|
||||
|
||||
buttonBox.add(Box.createHorizontalGlue());
|
||||
buttonBox.add(showUsageButton);
|
||||
if (!Platform.isMacOS()) {
|
||||
buttonBox.add(Box.createHorizontalStrut(GAP));
|
||||
}
|
||||
buttonBox.add(renameButton);
|
||||
buttonBox.add(Box.createHorizontalGlue());
|
||||
|
||||
Dimension showDim = showUsageButton.getPreferredSize();
|
||||
Dimension renameDim = renameButton.getPreferredSize();
|
||||
final int niceSize = Math.max(showDim.width, renameDim.width) + GAP;
|
||||
final Dimension buttonDim = new Dimension(niceSize, showDim.height);
|
||||
showUsageButton.setPreferredSize(buttonDim);
|
||||
renameButton.setPreferredSize(buttonDim);
|
||||
|
||||
windowBox.add(buttonBox);
|
||||
|
||||
//window.add(panelBottom);
|
||||
}
|
||||
window.add(windowBox);
|
||||
window.pack();
|
||||
//window.setMinimumSize(window.getSize());
|
||||
}
|
||||
|
||||
|
||||
// Thread: EDT
|
||||
void handleRename() {
|
||||
int startOffset = editor.getSelectionStart();
|
||||
int stopOffset = editor.getSelectionStop();
|
||||
int tabIndex = editor.getSketch().getCurrentCodeIndex();
|
||||
|
||||
pps.whenDoneBlocking(ps -> handleRename(ps, tabIndex, startOffset, stopOffset));
|
||||
}
|
||||
|
||||
|
||||
// Thread: worker
|
||||
void handleRename(PreprocessedSketch ps, int tabIndex, int startTabOffset, int stopTabOffset) {
|
||||
if (ps.hasSyntaxErrors) {
|
||||
editor.statusMessage("Cannot rename until syntax errors are fixed",
|
||||
EditorStatus.WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
ASTNode root = ps.compilationUnit;
|
||||
|
||||
// Map offsets
|
||||
int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset);
|
||||
int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset);
|
||||
|
||||
// Find the node
|
||||
SimpleName name = getSimpleNameAt(root, startJavaOffset, stopJavaOffset);
|
||||
if (name == null) {
|
||||
editor.statusMessage("Highlight the class/function/variable name first",
|
||||
EditorStatus.NOTICE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find binding
|
||||
IBinding binding = resolveBinding(name);
|
||||
if (binding == null) {
|
||||
editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " +
|
||||
"so it cannot be renamed", EditorStatus.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
ASTNode decl = ps.compilationUnit.findDeclaringNode(binding.getKey());
|
||||
if (decl == null) {
|
||||
editor.statusMessage(name.getIdentifier() + " isn't defined in this sketch, " +
|
||||
"so it cannot be renamed", EditorStatus.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Display the rename dialog
|
||||
EventQueue.invokeLater(() -> {
|
||||
if (!window.isVisible()) {
|
||||
this.ps = ps;
|
||||
this.binding = binding;
|
||||
oldNameLabel.setText("Current name: " + binding.getName());
|
||||
textField.setText(binding.getName());
|
||||
textField.requestFocus();
|
||||
textField.selectAll();
|
||||
int x = editor.getX() + (editor.getWidth() - window.getWidth()) / 2;
|
||||
int y = editor.getY() + (editor.getHeight() - window.getHeight()) / 2;
|
||||
window.setLocation(x, y);
|
||||
window.setVisible(true);
|
||||
window.toFront();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Thread: EDT (we can't allow user to mess with sketch while renaming)
|
||||
void rename(PreprocessedSketch ps, IBinding binding, String newName) {
|
||||
CompilationUnit root = ps.compilationUnit;
|
||||
|
||||
// Renaming constructor should rename class
|
||||
if (binding.getKind() == IBinding.METHOD) {
|
||||
IMethodBinding method = (IMethodBinding) binding;
|
||||
if (method.isConstructor()) {
|
||||
binding = method.getDeclaringClass();
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode decl = root.findDeclaringNode(binding.getKey());
|
||||
if (decl == null) return;
|
||||
|
||||
showUsage.hide();
|
||||
|
||||
List<SimpleName> occurrences = new ArrayList<>();
|
||||
occurrences.addAll(findAllOccurrences(root, binding.getKey()));
|
||||
|
||||
// Renaming class should rename all constructors
|
||||
if (binding.getKind() == IBinding.TYPE) {
|
||||
ITypeBinding type = (ITypeBinding) binding;
|
||||
//type = type.getErasure();
|
||||
IMethodBinding[] methods = type.getDeclaredMethods();
|
||||
Arrays.stream(methods)
|
||||
.filter(IMethodBinding::isConstructor)
|
||||
.flatMap(c -> findAllOccurrences(root, c.getKey()).stream())
|
||||
.forEach(occurrences::add);
|
||||
}
|
||||
|
||||
Map<Integer, List<SketchInterval>> mappedNodes = occurrences.stream()
|
||||
.map(ps::mapJavaToSketch)
|
||||
.filter(ps::inRange)
|
||||
.collect(Collectors.groupingBy(interval -> interval.tabIndex));
|
||||
|
||||
Sketch sketch = ps.sketch;
|
||||
|
||||
editor.startCompoundEdit();
|
||||
|
||||
mappedNodes.entrySet().forEach(entry -> {
|
||||
int tabIndex = entry.getKey();
|
||||
SketchCode sketchCode = sketch.getCode(tabIndex);
|
||||
|
||||
SyntaxDocument document = (SyntaxDocument) sketchCode.getDocument();
|
||||
|
||||
List<SketchInterval> nodes = entry.getValue();
|
||||
nodes.stream()
|
||||
// Replace from the end so all unprocess offsets stay valid
|
||||
.sorted(Comparator.comparing((SketchInterval si) -> si.startTabOffset).reversed())
|
||||
.forEach(si -> {
|
||||
// Make sure offsets are in bounds
|
||||
int documentLength = document.getLength();
|
||||
if (si.startTabOffset >= 0 && si.startTabOffset <= documentLength &&
|
||||
si.stopTabOffset >= 0 && si.stopTabOffset <= documentLength) {
|
||||
// Replace the code
|
||||
int length = si.stopTabOffset - si.startTabOffset;
|
||||
try {
|
||||
document.remove(si.startTabOffset, length);
|
||||
document.insertString(si.startTabOffset, newName, null);
|
||||
} catch (BadLocationException e) { /* Whatever */ }
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
sketchCode.setProgram(document.getText(0, document.getLength()));
|
||||
} catch (BadLocationException e) { /* Whatever */ }
|
||||
sketchCode.setModified(true);
|
||||
});
|
||||
|
||||
editor.stopCompoundEdit();
|
||||
|
||||
editor.repaintHeader();
|
||||
|
||||
int currentTabIndex = sketch.getCurrentCodeIndex();
|
||||
final int currentOffset = editor.getCaretOffset();
|
||||
|
||||
int precedingIntervals =
|
||||
(int) mappedNodes.getOrDefault(currentTabIndex, Collections.emptyList())
|
||||
.stream()
|
||||
.filter(interval -> interval.stopTabOffset < currentOffset)
|
||||
.count();
|
||||
int intervalLengthDiff = newName.length() - binding.getName().length();
|
||||
int offsetDiff = precedingIntervals * intervalLengthDiff;
|
||||
|
||||
editor.getTextArea().setCaretPosition(currentOffset + offsetDiff);
|
||||
}
|
||||
|
||||
|
||||
void dispose() {
|
||||
if (window != null) {
|
||||
window.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import static processing.mode.java.pdex.ASTUtils.findAllOccurrences;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeModel;
|
||||
|
||||
import org.eclipse.jdt.core.dom.IBinding;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.IVariableBinding;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
|
||||
import processing.app.Language;
|
||||
import processing.app.ui.EditorStatus;
|
||||
import processing.app.ui.Toolkit;
|
||||
import processing.app.ui.ZoomTreeCellRenderer;
|
||||
import processing.mode.java.JavaEditor;
|
||||
import processing.mode.java.pdex.PreprocessedSketch.SketchInterval;
|
||||
|
||||
|
||||
class ShowUsage {
|
||||
final JDialog window;
|
||||
final JTree tree;
|
||||
|
||||
final JavaEditor editor;
|
||||
final PreprocessingService pps;
|
||||
|
||||
final Consumer<PreprocessedSketch> reloadListener;
|
||||
|
||||
IBinding binding;
|
||||
|
||||
|
||||
ShowUsage(JavaEditor editor, PreprocessingService pps) {
|
||||
this.editor = editor;
|
||||
this.pps = pps;
|
||||
|
||||
// Add show usage option
|
||||
JMenuItem showUsageItem =
|
||||
new JMenuItem(Language.text("editor.popup.show_usage"));
|
||||
showUsageItem.addActionListener(e -> handleShowUsage());
|
||||
editor.getTextArea().getRightClickPopup().add(showUsageItem);
|
||||
|
||||
reloadListener = this::reloadShowUsage;
|
||||
|
||||
{ // Show Usage window
|
||||
window = new JDialog(editor);
|
||||
window.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
|
||||
window.setAutoRequestFocus(false);
|
||||
window.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
binding = null;
|
||||
tree.setModel(null);
|
||||
pps.unregisterListener(reloadListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
pps.registerListener(reloadListener);
|
||||
}
|
||||
});
|
||||
window.setSize(Toolkit.zoom(300, 400));
|
||||
window.setFocusableWindowState(false);
|
||||
Toolkit.setIcon(window);
|
||||
JScrollPane sp2 = new JScrollPane();
|
||||
tree = new JTree();
|
||||
ZoomTreeCellRenderer renderer =
|
||||
new ZoomTreeCellRenderer(editor.getMode());
|
||||
tree.setCellRenderer(renderer);
|
||||
renderer.setLeafIcon(null);
|
||||
renderer.setClosedIcon(null);
|
||||
renderer.setOpenIcon(null);
|
||||
renderer.setBackgroundSelectionColor(new Color(228, 248, 246));
|
||||
renderer.setBorderSelectionColor(new Color(0, 0, 0, 0));
|
||||
renderer.setTextSelectionColor(Color.BLACK);
|
||||
sp2.setViewportView(tree);
|
||||
window.add(sp2);
|
||||
}
|
||||
|
||||
tree.addTreeSelectionListener(e -> {
|
||||
if (tree.getLastSelectedPathComponent() != null) {
|
||||
DefaultMutableTreeNode tnode =
|
||||
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
|
||||
|
||||
if (tnode.getUserObject() instanceof ShowUsageTreeNode) {
|
||||
ShowUsageTreeNode node = (ShowUsageTreeNode) tnode.getUserObject();
|
||||
editor.highlight(node.tabIndex, node.startTabOffset, node.stopTabOffset);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Thread: EDT
|
||||
void handleShowUsage() {
|
||||
int startOffset = editor.getSelectionStart();
|
||||
int stopOffset = editor.getSelectionStop();
|
||||
int tabIndex = editor.getSketch().getCurrentCodeIndex();
|
||||
|
||||
pps.whenDoneBlocking(ps -> handleShowUsage(ps, tabIndex, startOffset, stopOffset));
|
||||
}
|
||||
|
||||
|
||||
// Thread: worker
|
||||
void handleShowUsage(PreprocessedSketch ps, int tabIndex,
|
||||
int startTabOffset, int stopTabOffset) {
|
||||
// Map offsets
|
||||
int startJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, startTabOffset);
|
||||
int stopJavaOffset = ps.tabOffsetToJavaOffset(tabIndex, stopTabOffset);
|
||||
|
||||
// Find the node
|
||||
SimpleName name = ASTUtils.getSimpleNameAt(ps.compilationUnit, startJavaOffset, stopJavaOffset);
|
||||
if (name == null) {
|
||||
editor.statusMessage("Cannot find any name under cursor", EditorStatus.NOTICE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find binding
|
||||
IBinding binding = ASTUtils.resolveBinding(name);
|
||||
if (binding == null) {
|
||||
editor.statusMessage("Cannot find usages, try to fix errors in your code first",
|
||||
EditorStatus.NOTICE);
|
||||
return;
|
||||
}
|
||||
|
||||
findUsageAndUpdateTree(ps, binding);
|
||||
}
|
||||
|
||||
|
||||
// Thread: worker
|
||||
void findUsageAndUpdateTree(PreprocessedSketch ps, IBinding binding) {
|
||||
|
||||
this.binding = binding;
|
||||
|
||||
// Get label
|
||||
String bindingType = "";
|
||||
switch (binding.getKind()) {
|
||||
case IBinding.METHOD:
|
||||
IMethodBinding method = (IMethodBinding) binding;
|
||||
if (method.isConstructor()) bindingType = "Constructor";
|
||||
else bindingType = "Method";
|
||||
break;
|
||||
case IBinding.TYPE:
|
||||
bindingType = "Type";
|
||||
break;
|
||||
case IBinding.VARIABLE:
|
||||
IVariableBinding variable = (IVariableBinding) binding;
|
||||
if (variable.isField()) bindingType = "Field";
|
||||
else if (variable.isParameter()) bindingType = "Parameter";
|
||||
else if (variable.isEnumConstant()) bindingType = "Enum constant";
|
||||
else bindingType = "Local variable";
|
||||
break;
|
||||
}
|
||||
|
||||
// Find usages, map to tree nodes, add to root node
|
||||
String bindingKey = binding.getKey();
|
||||
List<SketchInterval> intervals =
|
||||
findAllOccurrences(ps.compilationUnit, bindingKey).stream()
|
||||
.map(ps::mapJavaToSketch)
|
||||
// remove occurrences which fall into generated header
|
||||
.filter(ps::inRange)
|
||||
// remove empty intervals (happens when occurence was inserted)
|
||||
.filter(in -> in.startPdeOffset < in.stopPdeOffset)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int usageCount = intervals.size();
|
||||
|
||||
// Get element name from PDE code if possible, otherwise use one from Java
|
||||
String elementName = intervals.stream()
|
||||
.findAny()
|
||||
.map(si -> ps.pdeCode.substring(si.startPdeOffset, si.stopPdeOffset))
|
||||
.orElseGet(binding::getName);
|
||||
|
||||
// Create root node
|
||||
DefaultMutableTreeNode rootNode =
|
||||
new DefaultMutableTreeNode(bindingType + ": " + elementName);
|
||||
|
||||
intervals.stream()
|
||||
// Convert to TreeNodes
|
||||
.map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in))
|
||||
// Group by tab index
|
||||
.collect(Collectors.groupingBy(node -> node.tabIndex))
|
||||
// Stream Map Entries of (tab index) <-> (List<ShowUsageTreeNode>)
|
||||
.entrySet().stream()
|
||||
// Sort by tab index
|
||||
.sorted(Comparator.comparing(Map.Entry::getKey))
|
||||
.map(entry -> {
|
||||
Integer tabIndex = entry.getKey();
|
||||
List<ShowUsageTreeNode> nodes = entry.getValue();
|
||||
|
||||
int count = nodes.size();
|
||||
String usageLabel = count == 1 ? "usage" : "usages";
|
||||
|
||||
// Create new DefaultMutableTreeNode for this tab
|
||||
String tabLabel = "<html><font color=#222222>" +
|
||||
ps.sketch.getCode(tabIndex).getPrettyName() +
|
||||
"</font> <font color=#999999>" + count + " " + usageLabel + "</font></html>";
|
||||
DefaultMutableTreeNode tabNode = new DefaultMutableTreeNode(tabLabel);
|
||||
|
||||
// Stream nodes belonging to this tab
|
||||
nodes.stream()
|
||||
// Convert TreeNodes to DefaultMutableTreeNodes
|
||||
.map(DefaultMutableTreeNode::new)
|
||||
// Add all as children of tab node
|
||||
.forEach(tabNode::add);
|
||||
return tabNode;
|
||||
})
|
||||
// Add all tab nodes as children of root node
|
||||
.forEach(rootNode::add);
|
||||
|
||||
TreeModel treeModel = new DefaultTreeModel(rootNode);
|
||||
|
||||
// Update tree
|
||||
EventQueue.invokeLater(() -> {
|
||||
tree.setModel(treeModel);
|
||||
|
||||
// Expand all nodes
|
||||
for (int i = 0; i < tree.getRowCount(); i++) {
|
||||
tree.expandRow(i);
|
||||
}
|
||||
|
||||
tree.setRootVisible(true);
|
||||
|
||||
if (!window.isVisible()) {
|
||||
window.setVisible(true);
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice defaultScreen = ge.getDefaultScreenDevice();
|
||||
Rectangle rect = defaultScreen.getDefaultConfiguration().getBounds();
|
||||
int maxX = (int) rect.getMaxX() - window.getWidth();
|
||||
int x = Math.min(editor.getX() + editor.getWidth(), maxX);
|
||||
int y = (x == maxX) ? 10 : editor.getY();
|
||||
window.setLocation(x, y);
|
||||
}
|
||||
window.toFront();
|
||||
window.setTitle("Usage of \"" + elementName + "\" : " +
|
||||
usageCount + " time(s)");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Thread: worker
|
||||
void reloadShowUsage(PreprocessedSketch ps) {
|
||||
if (binding != null) {
|
||||
findUsageAndUpdateTree(ps, binding);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void hide() {
|
||||
window.setVisible(false);
|
||||
}
|
||||
|
||||
|
||||
void dispose() {
|
||||
if (window != null) {
|
||||
window.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ShowUsageTreeNode {
|
||||
final int tabIndex;
|
||||
final int startTabOffset;
|
||||
final int stopTabOffset;
|
||||
|
||||
final String text;
|
||||
|
||||
|
||||
ShowUsageTreeNode(int tabIndex, int startTabOffset, int stopTabOffset, String text) {
|
||||
this.tabIndex = tabIndex;
|
||||
this.startTabOffset = startTabOffset;
|
||||
this.stopTabOffset = stopTabOffset;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
|
||||
static ShowUsageTreeNode fromSketchInterval(PreprocessedSketch ps, SketchInterval in) {
|
||||
int lineStartPdeOffset = ps.pdeCode.lastIndexOf('\n', in.startPdeOffset) + 1;
|
||||
int lineStopPdeOffset = ps.pdeCode.indexOf('\n', in.stopPdeOffset);
|
||||
if (lineStopPdeOffset == -1) lineStopPdeOffset = ps.pdeCode.length();
|
||||
|
||||
int highlightStartOffset = in.startPdeOffset - lineStartPdeOffset;
|
||||
int highlightStopOffset = in.stopPdeOffset - lineStartPdeOffset;
|
||||
|
||||
int tabLine = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
|
||||
|
||||
// TODO: what a mess
|
||||
String line = ps.pdeCode.substring(lineStartPdeOffset, lineStopPdeOffset);
|
||||
String pre = line.substring(0, highlightStartOffset)
|
||||
.replace("&", "&").replace(">", ">").replace("<", "<");
|
||||
String highlight = line.substring(highlightStartOffset, highlightStopOffset)
|
||||
.replace("&", "&").replace(">", ">").replace("<", "<");
|
||||
String post = line.substring(highlightStopOffset)
|
||||
.replace("&", "&").replace(">", ">").replace("<", "<");
|
||||
line = pre + "<font color=#222222><b>" + highlight + "</b></font>" + post;
|
||||
line = line.trim();
|
||||
|
||||
|
||||
String text = "<html><font color=#bbbbbb>" +
|
||||
(tabLine + 1) + "</font> <font color=#777777>" + line + "</font></html>";
|
||||
|
||||
return new ShowUsageTreeNode(in.tabIndex, in.startTabOffset, in.stopTabOffset, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.NumberLiteral;
|
||||
import org.eclipse.jdt.core.dom.SimpleType;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import processing.mode.java.pdex.TextTransform.Edit;
|
||||
import processing.mode.java.preproc.PdePreprocessor;
|
||||
|
||||
public class SourceUtils {
|
||||
|
||||
|
||||
public static final Pattern IMPORT_REGEX =
|
||||
Pattern.compile("(?:^|;)\\s*(import\\s+(?:(static)\\s+)?((?:\\w+\\s*\\.)*)\\s*(\\S+)\\s*;)",
|
||||
Pattern.MULTILINE | Pattern.DOTALL);
|
||||
|
||||
public static final Pattern IMPORT_REGEX_NO_KEYWORD =
|
||||
Pattern.compile("^\\s*((?:(static)\\s+)?((?:\\w+\\s*\\.)*)\\s*(\\S+))",
|
||||
Pattern.MULTILINE | Pattern.DOTALL);
|
||||
|
||||
public static List<ImportStatement> parseProgramImports(CharSequence source) {
|
||||
List<ImportStatement> result = new ArrayList<>();
|
||||
Matcher matcher = IMPORT_REGEX.matcher(source);
|
||||
while (matcher.find()) {
|
||||
ImportStatement is = ImportStatement.parse(matcher.toMatchResult());
|
||||
result.add(is);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Edit> parseProgramImports(CharSequence source,
|
||||
List<ImportStatement> outImports) {
|
||||
List<Edit> result = new ArrayList<>();
|
||||
Matcher matcher = IMPORT_REGEX.matcher(source);
|
||||
while (matcher.find()) {
|
||||
ImportStatement is = ImportStatement.parse(matcher.toMatchResult());
|
||||
outImports.add(is);
|
||||
int idx = matcher.start(1);
|
||||
int len = matcher.end(1) - idx;
|
||||
// Remove the import from the main program
|
||||
// Substitute with white spaces
|
||||
result.add(Edit.move(idx, len, 0));
|
||||
result.add(Edit.insert(0, "\n"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Positive lookahead and lookbehind are needed to match all type constructors
|
||||
// in code like `int(byte(245))` where first bracket matches as last
|
||||
// group in "^int(" but also as a first group in "(byte(". Lookahead and
|
||||
// lookbehind won't consume the shared character.
|
||||
public static final Pattern TYPE_CONSTRUCTOR_REGEX =
|
||||
Pattern.compile("(?<=^|\\W)(int|char|float|boolean|byte)(?=\\s*\\()",
|
||||
Pattern.MULTILINE);
|
||||
|
||||
public static List<Edit> replaceTypeConstructors(CharSequence source) {
|
||||
|
||||
List<Edit> result = new ArrayList<>();
|
||||
|
||||
Matcher matcher = TYPE_CONSTRUCTOR_REGEX.matcher(source);
|
||||
while (matcher.find()) {
|
||||
String match = matcher.group(1);
|
||||
int offset = matcher.start(1);
|
||||
int length = match.length();
|
||||
result.add(Edit.insert(offset, "PApplet."));
|
||||
String replace = "parse"
|
||||
+ Character.toUpperCase(match.charAt(0)) + match.substring(1);
|
||||
result.add(Edit.replace(offset, length, replace));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static final Pattern HEX_LITERAL_REGEX =
|
||||
Pattern.compile("(?<=^|\\W)(#[A-Fa-f0-9]{6})(?=\\W|$)");
|
||||
|
||||
public static List<Edit> replaceHexLiterals(CharSequence source) {
|
||||
// Find all #[webcolor] and replace with 0xff[webcolor]
|
||||
// Should be 6 digits only.
|
||||
List<Edit> result = new ArrayList<>();
|
||||
|
||||
Matcher matcher = HEX_LITERAL_REGEX.matcher(source);
|
||||
while (matcher.find()) {
|
||||
int offset = matcher.start(1);
|
||||
result.add(Edit.replace(offset, 1, "0xff"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static List<Edit> insertImports(List<ImportStatement> imports) {
|
||||
List<Edit> result = new ArrayList<>();
|
||||
for (ImportStatement imp : imports) {
|
||||
result.add(Edit.insert(0, imp.getFullSourceLine() + "\n"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Edit> wrapSketch(PdePreprocessor.Mode mode, String className, int sourceLength) {
|
||||
|
||||
List<Edit> edits = new ArrayList<>();
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
||||
// Header
|
||||
if (mode != PdePreprocessor.Mode.JAVA) {
|
||||
b.append("\npublic class ").append(className).append(" extends PApplet {\n");
|
||||
if (mode == PdePreprocessor.Mode.STATIC) {
|
||||
b.append("public void setup() {\n");
|
||||
}
|
||||
}
|
||||
|
||||
edits.add(Edit.insert(0, b.toString()));
|
||||
|
||||
// Reset builder
|
||||
b.setLength(0);
|
||||
|
||||
// Footer
|
||||
if (mode != PdePreprocessor.Mode.JAVA) {
|
||||
if (mode == PdePreprocessor.Mode.STATIC) {
|
||||
// no noLoop() here so it does not tell you
|
||||
// "can't invoke noLoop() on obj" when you type "obj."
|
||||
b.append("\n}");
|
||||
}
|
||||
b.append("\n}\n");
|
||||
}
|
||||
|
||||
edits.add(Edit.insert(sourceLength, b.toString()));
|
||||
|
||||
return edits;
|
||||
}
|
||||
|
||||
|
||||
// Verifies that whole input String is floating point literal. Can't be used for searching.
|
||||
// https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-DecimalFloatingPointLiteral
|
||||
public static final Pattern FLOATING_POINT_LITERAL_VERIFIER;
|
||||
static {
|
||||
final String DIGITS = "(?:[0-9]|[0-9][0-9_]*[0-9])";
|
||||
final String EXPONENT_PART = "(?:[eE][+-]?" + DIGITS + ")";
|
||||
FLOATING_POINT_LITERAL_VERIFIER = Pattern.compile(
|
||||
"(?:^" + DIGITS + "\\." + DIGITS + "?" + EXPONENT_PART + "?[fFdD]?$)|" +
|
||||
"(?:^\\." + DIGITS + EXPONENT_PART + "?[fFdD]?$)|" +
|
||||
"(?:^" + DIGITS + EXPONENT_PART + "[fFdD]?$)|" +
|
||||
"(?:^" + DIGITS + EXPONENT_PART + "?[fFdD]$)");
|
||||
}
|
||||
|
||||
// Mask to quickly resolve whether there are any access modifiers present
|
||||
private static final int ACCESS_MODIFIERS_MASK =
|
||||
Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED;
|
||||
|
||||
public static List<Edit> preprocessAST(CompilationUnit cu) {
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
|
||||
// Walk the tree
|
||||
cu.accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(SimpleType node) {
|
||||
// replace "color" with "int"
|
||||
if ("color".equals(node.getName().toString())) {
|
||||
edits.add(Edit.replace(node.getStartPosition(), node.getLength(), "int"));
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(NumberLiteral node) {
|
||||
// add 'f' to floats
|
||||
String s = node.getToken().toLowerCase();
|
||||
if (FLOATING_POINT_LITERAL_VERIFIER.matcher(s).matches() && !s.endsWith("f") && !s.endsWith("d")) {
|
||||
edits.add(Edit.insert(node.getStartPosition() + node.getLength(), "f"));
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodDeclaration node) {
|
||||
// add 'public' to methods with default visibility
|
||||
int accessModifiers = node.getModifiers() & ACCESS_MODIFIERS_MASK;
|
||||
if (accessModifiers == 0) {
|
||||
edits.add(Edit.insert(node.getStartPosition(), "public "));
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
|
||||
return edits;
|
||||
}
|
||||
|
||||
|
||||
public static final Pattern COLOR_TYPE_REGEX =
|
||||
Pattern.compile("(?:^|^\\p{javaJavaIdentifierPart})(color)\\s(?!\\s*\\()",
|
||||
Pattern.MULTILINE | Pattern.UNICODE_CHARACTER_CLASS);
|
||||
|
||||
public static List<Edit> replaceColorRegex(CharSequence source) {
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
|
||||
Matcher matcher = COLOR_TYPE_REGEX.matcher(source);
|
||||
while (matcher.find()) {
|
||||
int offset = matcher.start(1);
|
||||
edits.add(Edit.replace(offset, 5, "int"));
|
||||
}
|
||||
|
||||
return edits;
|
||||
}
|
||||
|
||||
|
||||
public static final Pattern NUMBER_LITERAL_REGEX =
|
||||
Pattern.compile("[-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?");
|
||||
|
||||
public static List<Edit> fixFloatsRegex(CharSequence source) {
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
|
||||
Matcher matcher = NUMBER_LITERAL_REGEX.matcher(source);
|
||||
while (matcher.find()) {
|
||||
int offset = matcher.start();
|
||||
int end = matcher.end();
|
||||
String group = matcher.group().toLowerCase();
|
||||
boolean isFloatingPoint = group.contains(".") || group.contains("e");
|
||||
boolean hasSuffix = end < source.length() &&
|
||||
Character.toLowerCase(source.charAt(end)) != 'f' &&
|
||||
Character.toLowerCase(source.charAt(end)) != 'd';
|
||||
if (isFloatingPoint && !hasSuffix) {
|
||||
edits.add(Edit.insert(offset, "f"));
|
||||
}
|
||||
}
|
||||
|
||||
return edits;
|
||||
}
|
||||
|
||||
|
||||
static public String scrubCommentsAndStrings(String p) {
|
||||
StringBuilder sb = new StringBuilder(p);
|
||||
scrubCommentsAndStrings(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static public void scrubCommentsAndStrings(StringBuilder p) {
|
||||
|
||||
final int length = p.length();
|
||||
|
||||
final int OUT = 0;
|
||||
final int IN_BLOCK_COMMENT = 1;
|
||||
final int IN_EOL_COMMENT = 2;
|
||||
final int IN_STRING_LITERAL = 3;
|
||||
final int IN_CHAR_LITERAL = 4;
|
||||
|
||||
int blockStart = -1;
|
||||
|
||||
int prevState = OUT;
|
||||
int state = OUT;
|
||||
|
||||
for (int i = 0; i <= length; i++) {
|
||||
char ch = (i < length) ? p.charAt(i) : 0;
|
||||
char pch = (i == 0) ? 0 : p.charAt(i-1);
|
||||
// Get rid of double backslash immediately, otherwise
|
||||
// the second backslash incorrectly triggers a new escape sequence
|
||||
if (pch == '\\' && ch == '\\') {
|
||||
p.setCharAt(i-1, ' ');
|
||||
p.setCharAt(i, ' ');
|
||||
pch = ' ';
|
||||
ch = ' ';
|
||||
}
|
||||
switch (state) {
|
||||
case OUT:
|
||||
switch (ch) {
|
||||
case '\'': state = IN_CHAR_LITERAL; break;
|
||||
case '"': state = IN_STRING_LITERAL; break;
|
||||
case '*': if (pch == '/') state = IN_BLOCK_COMMENT; break;
|
||||
case '/': if (pch == '/') state = IN_EOL_COMMENT; break;
|
||||
}
|
||||
break;
|
||||
case IN_BLOCK_COMMENT:
|
||||
if (pch == '*' && ch == '/' && (i - blockStart) > 0) {
|
||||
state = OUT;
|
||||
}
|
||||
break;
|
||||
case IN_EOL_COMMENT:
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
state = OUT;
|
||||
}
|
||||
break;
|
||||
case IN_STRING_LITERAL:
|
||||
if ((pch != '\\' && ch == '"') || ch == '\r' || ch == '\n') {
|
||||
state = OUT;
|
||||
}
|
||||
break;
|
||||
case IN_CHAR_LITERAL:
|
||||
if ((pch != '\\' && ch == '\'') || ch == '\r' || ch == '\n') {
|
||||
state = OUT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Terminate ongoing block at last char
|
||||
if (i == length) {
|
||||
state = OUT;
|
||||
}
|
||||
|
||||
// Handle state changes
|
||||
if (state != prevState) {
|
||||
if (state != OUT) {
|
||||
// Entering block
|
||||
blockStart = i + 1;
|
||||
} else {
|
||||
// Exiting block
|
||||
int blockEnd = i;
|
||||
if (prevState == IN_BLOCK_COMMENT && i < length) blockEnd--; // preserve star in '*/'
|
||||
for (int j = blockStart; j < blockEnd; j++) {
|
||||
char c = p.charAt(j);
|
||||
if (c != '\n' && c != '\r') p.setCharAt(j, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevState = state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TODO: move this to a better place when JavaBuild starts using JDT and we
|
||||
// don't need to check errors at two different places [jv 2017-09-19]
|
||||
/**
|
||||
* Checks a single code fragment (such as a tab) for non-matching braces.
|
||||
* Broken out to allow easy use in JavaBuild.
|
||||
* @param c Program code scrubbed of comments and string literals.
|
||||
* @param start Start index, inclusive.
|
||||
* @param end End index, exclusive.
|
||||
* @return {@code int[4]} Depth at which the loop stopped, followed by the
|
||||
* line number, column, and string index (within the range) at which
|
||||
* an error was found, if any.
|
||||
*/
|
||||
static public int[] checkForMissingBraces(CharSequence c, int start, int end) {
|
||||
int depth = 0;
|
||||
int lineNumber = 0;
|
||||
int lineStart = start;
|
||||
for (int i = start; i < end; i++) {
|
||||
char ch = c.charAt(i);
|
||||
switch (ch) {
|
||||
case '{':
|
||||
depth++;
|
||||
break;
|
||||
case '}':
|
||||
depth--;
|
||||
break;
|
||||
case '\n':
|
||||
lineNumber++;
|
||||
lineStart = i;
|
||||
break;
|
||||
}
|
||||
if (depth < 0) {
|
||||
return new int[] {depth, lineNumber, i - lineStart, i - start};
|
||||
}
|
||||
}
|
||||
return new int[] {depth, lineNumber - 1, end - lineStart - 2, end - start - 2};
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
package processing.mode.java.pdex;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import processing.core.PApplet;
|
||||
|
||||
|
||||
public class TextTransform {
|
||||
|
||||
private static final Comparator<Edit> INPUT_OFFSET_COMP =
|
||||
(o1, o2) -> Integer.compare(o1.fromOffset, o2.fromOffset);
|
||||
|
||||
private static final Comparator<Edit> OUTPUT_OFFSET_COMP =
|
||||
(o1, o2) -> Integer.compare(o1.toOffset, o2.toOffset);
|
||||
|
||||
|
||||
private CharSequence input;
|
||||
|
||||
private List<Edit> edits = new ArrayList<>();
|
||||
|
||||
private List<Edit> inMap = new ArrayList<>();
|
||||
private List<Edit> outMap = new ArrayList<>();
|
||||
|
||||
private boolean built;
|
||||
private int builtForLength;
|
||||
|
||||
|
||||
TextTransform(CharSequence input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
|
||||
public void add(Edit edit) {
|
||||
edits.add(edit);
|
||||
built = false;
|
||||
}
|
||||
|
||||
|
||||
public void addAll(Collection<Edit> edits) {
|
||||
this.edits.addAll(edits);
|
||||
built = false;
|
||||
}
|
||||
|
||||
|
||||
public String apply() {
|
||||
final int inLength = input.length();
|
||||
final StringBuilder output = new StringBuilder(inLength);
|
||||
|
||||
buildIfNeeded(inLength);
|
||||
|
||||
outMap.stream()
|
||||
// Filter out Delete edits
|
||||
.filter(outEdit -> outEdit.toLength > 0)
|
||||
.forEach(outEdit -> {
|
||||
if (outEdit.outputText != null) {
|
||||
// Insert or Replace edit
|
||||
output.append(outEdit.outputText);
|
||||
} else {
|
||||
// Move edit
|
||||
output.append(input, outEdit.fromOffset, outEdit.fromOffset + outEdit.fromLength);
|
||||
}
|
||||
});
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
|
||||
public OffsetMapper getMapper() {
|
||||
int inLength = input.length();
|
||||
buildIfNeeded(inLength);
|
||||
return new SimpleOffsetMapper(inMap, outMap);
|
||||
}
|
||||
|
||||
|
||||
private void buildIfNeeded(int inLength) {
|
||||
if (built && inLength == builtForLength) return;
|
||||
|
||||
// Make copies of Edits to preserve original edits
|
||||
List<Edit> inEdits = edits.stream().map(Edit::new).collect(Collectors.toList());
|
||||
List<Edit> outEdits = new ArrayList<>(inEdits);
|
||||
|
||||
// Edits sorted by input offsets
|
||||
Collections.sort(inEdits, INPUT_OFFSET_COMP);
|
||||
|
||||
// Edits sorted by output offsets
|
||||
Collections.sort(outEdits, OUTPUT_OFFSET_COMP);
|
||||
|
||||
// TODO: add some validation
|
||||
|
||||
// Input
|
||||
ListIterator<Edit> inIt = inEdits.listIterator();
|
||||
Edit inEdit = inIt.hasNext() ? inIt.next() : null;
|
||||
int inEditOff = inEdit == null ? inLength : inEdit.fromOffset;
|
||||
|
||||
// Output
|
||||
ListIterator<Edit> outIt = outEdits.listIterator();
|
||||
Edit outEdit = outIt.hasNext() ? outIt.next() : null;
|
||||
int outEditOff = outEdit == null ? inLength : outEdit.toOffset;
|
||||
|
||||
int inOffset = 0;
|
||||
int outOffset = 0;
|
||||
|
||||
inMap.clear();
|
||||
outMap.clear();
|
||||
|
||||
// Walk through the input, apply changes, create mapping
|
||||
while (inOffset < inLength || inEdit != null || outEdit != null) {
|
||||
|
||||
{ // Create mapping for unmodified portion of the input
|
||||
int nextEditOffset = Math.min(inEditOff, outEditOff);
|
||||
|
||||
{ // Insert move block to have mapping for unmodified portions too
|
||||
int length = nextEditOffset - inOffset;
|
||||
if (length > 0) {
|
||||
Edit ch = Edit.move(inOffset, length, outOffset);
|
||||
inMap.add(ch);
|
||||
outMap.add(ch);
|
||||
}
|
||||
}
|
||||
|
||||
// Move offsets accordingly
|
||||
outOffset += nextEditOffset - inOffset;
|
||||
inOffset = nextEditOffset;
|
||||
}
|
||||
|
||||
// Process encountered input edits
|
||||
while (inEdit != null && inOffset >= inEditOff) {
|
||||
inOffset += inEdit.fromLength;
|
||||
if (inEdit.fromLength > 0) inMap.add(inEdit);
|
||||
inEdit = inIt.hasNext() ? inIt.next() : null;
|
||||
inEditOff = inEdit != null ? inEdit.fromOffset : inLength;
|
||||
}
|
||||
|
||||
// Process encountered output edits
|
||||
while (outEdit != null && inOffset >= outEditOff) {
|
||||
outEdit.toOffset = outOffset;
|
||||
if (outEdit.toLength > 0) outMap.add(outEdit);
|
||||
outOffset += outEdit.toLength;
|
||||
outEdit = outIt.hasNext() ? outIt.next() : null;
|
||||
outEditOff = outEdit != null ? outEdit.toOffset : inLength;
|
||||
}
|
||||
}
|
||||
|
||||
built = true;
|
||||
builtForLength = inLength;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SourceMapping{" +
|
||||
"edits=" + edits +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
protected static class Edit {
|
||||
|
||||
static Edit insert(int offset, String text) {
|
||||
return new Edit(offset, 0, offset, text.length(), text);
|
||||
}
|
||||
|
||||
static Edit replace(int offset, int length, String text) {
|
||||
return new Edit(offset, length, offset, text.length(), text);
|
||||
}
|
||||
|
||||
static Edit move(int fromOffset, int length, int toOffset) {
|
||||
Edit result = new Edit(fromOffset, length, toOffset, length, null);
|
||||
result.toOffset = toOffset;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Edit delete(int position, int length) {
|
||||
return new Edit(position, length, position, 0, null);
|
||||
}
|
||||
|
||||
Edit(Edit edit) {
|
||||
this.fromOffset = edit.fromOffset;
|
||||
this.fromLength = edit.fromLength;
|
||||
this.toOffset = edit.toOffset;
|
||||
this.toLength = edit.toLength;
|
||||
this.outputText = edit.outputText;
|
||||
}
|
||||
|
||||
Edit(int fromOffset, int fromLength, int toOffset, int toLength, String text) {
|
||||
this.fromOffset = fromOffset;
|
||||
this.fromLength = fromLength;
|
||||
this.toOffset = toOffset;
|
||||
this.toLength = toLength;
|
||||
this.outputText = text;
|
||||
}
|
||||
|
||||
private final int fromOffset;
|
||||
private final int fromLength;
|
||||
private int toOffset;
|
||||
private final int toLength;
|
||||
private final String outputText;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Edit{" +
|
||||
"from=" + fromOffset + ":" + fromLength +
|
||||
", to=" + toOffset + ":" + toLength +
|
||||
((outputText != null) ? (", text='" + outputText + '\'') : "") +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected interface OffsetMapper {
|
||||
int getInputOffset(int outputOffset);
|
||||
int getOutputOffset(int inputOffset);
|
||||
OffsetMapper thenMapping(OffsetMapper mapper);
|
||||
OffsetMapper EMPTY_MAPPER = CompositeOffsetMapper.of();
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleOffsetMapper implements OffsetMapper {
|
||||
private List<Edit> inMap = new ArrayList<>();
|
||||
private List<Edit> outMap = new ArrayList<>();
|
||||
|
||||
private int outputOffsetOfInputStart;
|
||||
private int inputOffsetOfOutputStart;
|
||||
|
||||
private SimpleOffsetMapper(List<Edit> inMap, List<Edit> outMap) {
|
||||
this.inMap.addAll(inMap);
|
||||
this.outMap.addAll(outMap);
|
||||
|
||||
Edit inStart = null;
|
||||
for (Edit in : this.inMap) {
|
||||
inStart = in;
|
||||
if (in.fromLength > 0) break;
|
||||
}
|
||||
outputOffsetOfInputStart = inStart == null ? 0 : inStart.toOffset;
|
||||
|
||||
Edit outStart = null;
|
||||
for (Edit out : this.inMap) {
|
||||
outStart = out;
|
||||
if (out.toLength > 0) break;
|
||||
}
|
||||
inputOffsetOfOutputStart = outStart == null ? 0 : outStart.fromOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInputOffset(int outputOffset) {
|
||||
if (outputOffset < outputOffsetOfInputStart) return -1;
|
||||
Edit searchKey = new Edit(0, 0, outputOffset, Integer.MAX_VALUE, null);
|
||||
int i = Collections.binarySearch(outMap, searchKey, OUTPUT_OFFSET_COMP);
|
||||
if (i < 0) {
|
||||
i = -(i + 1);
|
||||
i -= 1;
|
||||
}
|
||||
i = PApplet.constrain(i, 0, outMap.size()-1);
|
||||
Edit edit = outMap.get(i);
|
||||
int diff = outputOffset - edit.toOffset;
|
||||
return edit.fromOffset + Math.min(diff, Math.max(0, edit.fromLength - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutputOffset(int inputOffset) {
|
||||
if (inputOffset < inputOffsetOfOutputStart) return -1;
|
||||
Edit searchKey = new Edit(inputOffset, Integer.MAX_VALUE, 0, 0, null);
|
||||
int i = Collections.binarySearch(inMap, searchKey, INPUT_OFFSET_COMP);
|
||||
if (i < 0) {
|
||||
i = -(i + 1);
|
||||
i -= 1;
|
||||
}
|
||||
i = PApplet.constrain(i, 0, inMap.size()-1);
|
||||
Edit edit = inMap.get(i);
|
||||
int diff = inputOffset - edit.fromOffset;
|
||||
return edit.toOffset + Math.min(diff, Math.max(0, edit.toLength - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OffsetMapper thenMapping(OffsetMapper mapper) {
|
||||
return CompositeOffsetMapper.of(this, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class CompositeOffsetMapper implements OffsetMapper {
|
||||
private List<OffsetMapper> mappers = new ArrayList<>();
|
||||
|
||||
public static CompositeOffsetMapper of(OffsetMapper... inMappers) {
|
||||
CompositeOffsetMapper composite = new CompositeOffsetMapper();
|
||||
|
||||
// Add mappers one by one, unwrap if Composite
|
||||
for (OffsetMapper mapper : inMappers) {
|
||||
if (mapper instanceof CompositeOffsetMapper) {
|
||||
composite.mappers.addAll(((CompositeOffsetMapper) mapper).mappers);
|
||||
} else {
|
||||
composite.mappers.add(mapper);
|
||||
}
|
||||
}
|
||||
|
||||
return composite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInputOffset(int outputOffset) {
|
||||
for (int i = mappers.size() - 1; i >= 0; i--) {
|
||||
outputOffset = mappers.get(i).getInputOffset(outputOffset);
|
||||
}
|
||||
return outputOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOutputOffset(int inputOffset) {
|
||||
for (OffsetMapper mapper : mappers) {
|
||||
inputOffset = mapper.getOutputOffset(inputOffset);
|
||||
}
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OffsetMapper thenMapping(OffsetMapper mapper) {
|
||||
return CompositeOffsetMapper.of(this, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/processing-app/test/src/test/processing/mode/java/ParserTests.java"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
|
||||
<mapEntry key="[run]" value="org.eclipse.jdt.junit.launchconfig"/>
|
||||
</mapAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value=""/>
|
||||
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
|
||||
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="test.processing.mode.java.ParserTests"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="processing-app"/>
|
||||
</launchConfiguration>
|
||||
+776
@@ -0,0 +1,776 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.BitSet;
|
||||
import java.util.Stack;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.SketchException;
|
||||
import processing.mode.java.preproc.PdeTokenTypes;
|
||||
import antlr.CommonASTWithHiddenTokens;
|
||||
import antlr.CommonHiddenStreamToken;
|
||||
import antlr.collections.AST;
|
||||
|
||||
/* Based on original code copyright (c) 2003 Andy Tripp <atripp@comcast.net>.
|
||||
* shipped under GPL with permission.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PDEEmitter: A class that can take an ANTLR Java AST and produce
|
||||
* reasonably formatted Java code from it. To use it, create a
|
||||
* PDEEmitter object, call setOut() if you want to print to something
|
||||
* other than System.out, and then call print(), passing the
|
||||
* AST. Typically, the AST node that you pass would be the root of a
|
||||
* tree - the ROOT_ID node that represents a Java file.
|
||||
*
|
||||
* Modified March 2010 to support Java 5 type arguments and for loops by
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*/
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class PdeEmitter implements PdeTokenTypes {
|
||||
private final PdePreprocessor pdePreprocessor;
|
||||
private final PrintWriter out;
|
||||
private final PrintStream debug = System.err;
|
||||
|
||||
private final Stack<AST> stack = new Stack<AST>();
|
||||
private final static int ROOT_ID = 0;
|
||||
|
||||
public PdeEmitter(final PdePreprocessor pdePreprocessor, final PrintWriter out) {
|
||||
this.pdePreprocessor = pdePreprocessor;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a child of the given AST that has the given type
|
||||
* @returns a child AST of the given type. If it can't find a child of the
|
||||
* given type, return null.
|
||||
*/
|
||||
static private AST getChild(final AST ast, final int childType) {
|
||||
AST child = ast.getFirstChild();
|
||||
while (child != null) {
|
||||
if (child.getType() == childType) {
|
||||
// debug.println("getChild: found:" + name(ast));
|
||||
return child;
|
||||
}
|
||||
child = child.getNextSibling();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the list of hidden tokens linked to after the AST node passed in.
|
||||
* Most hidden tokens are dumped from this function.
|
||||
*/
|
||||
private void dumpHiddenAfter(final AST ast) {
|
||||
dumpHiddenTokens(((CommonASTWithHiddenTokens) ast).getHiddenAfter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the list of hidden tokens linked to before the AST node passed in.
|
||||
* The only time hidden tokens need to be dumped with this function is when
|
||||
* dealing parts of the tree where automatic tree construction was
|
||||
* turned off with the ! operator in the grammar file and the nodes were
|
||||
* manually constructed in such a way that the usual tokens don't have the
|
||||
* necessary hiddenAfter links.
|
||||
*/
|
||||
private void dumpHiddenBefore(final AST ast) {
|
||||
|
||||
antlr.CommonHiddenStreamToken child = null, parent = ((CommonASTWithHiddenTokens) ast)
|
||||
.getHiddenBefore();
|
||||
|
||||
// if there aren't any hidden tokens here, quietly return
|
||||
//
|
||||
if (parent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// traverse back to the head of the list of tokens before this node
|
||||
do {
|
||||
child = parent;
|
||||
parent = child.getHiddenBefore();
|
||||
} while (parent != null);
|
||||
|
||||
// dump that list
|
||||
dumpHiddenTokens(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the list of hidden tokens linked to from the token passed in.
|
||||
*/
|
||||
private void dumpHiddenTokens(CommonHiddenStreamToken t) {
|
||||
for (; t != null; t = pdePreprocessor.getHiddenAfter(t)) {
|
||||
out.print(t.getText());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the children of the given AST
|
||||
* @param ast The AST to print
|
||||
* @returns true iff anything was printed
|
||||
*/
|
||||
private boolean printChildren(final AST ast) throws SketchException {
|
||||
boolean ret = false;
|
||||
AST child = ast.getFirstChild();
|
||||
while (child != null) {
|
||||
ret = true;
|
||||
print(child);
|
||||
child = child.getNextSibling();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether an AST has any children or not.
|
||||
* @return true iff the AST has at least one child
|
||||
*/
|
||||
static private boolean hasChildren(final AST ast) {
|
||||
return (ast.getFirstChild() != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the best node in the subtree for printing. This really means
|
||||
* the next node which could potentially have hiddenBefore data. It's
|
||||
* usually the first printable leaf, but not always.
|
||||
*
|
||||
* @param includeThisNode Should this node be included in the search?
|
||||
* If false, only descendants are searched.
|
||||
*
|
||||
* @return the first printable leaf node in an AST
|
||||
*/
|
||||
private AST getBestPrintableNode(final AST ast, final boolean includeThisNode) {
|
||||
AST child;
|
||||
|
||||
if (includeThisNode) {
|
||||
child = ast;
|
||||
} else {
|
||||
child = ast.getFirstChild();
|
||||
}
|
||||
|
||||
if (child != null) {
|
||||
|
||||
switch (child.getType()) {
|
||||
|
||||
// the following node types are printing nodes that print before
|
||||
// any children, but then also recurse over children. So they
|
||||
// may have hiddenBefore chains that need to be printed first. Many
|
||||
// statements and all unary expression types qualify. Return these
|
||||
// nodes directly
|
||||
case CLASS_DEF:
|
||||
case ENUM_DEF:
|
||||
case LITERAL_if:
|
||||
case LITERAL_new:
|
||||
case LITERAL_for:
|
||||
case LITERAL_while:
|
||||
case LITERAL_do:
|
||||
case LITERAL_break:
|
||||
case LITERAL_continue:
|
||||
case LITERAL_return:
|
||||
case LITERAL_switch:
|
||||
case LITERAL_try:
|
||||
case LITERAL_throw:
|
||||
case LITERAL_synchronized:
|
||||
case LITERAL_assert:
|
||||
case BNOT:
|
||||
case LNOT:
|
||||
case INC:
|
||||
case DEC:
|
||||
case UNARY_MINUS:
|
||||
case UNARY_PLUS:
|
||||
return child;
|
||||
|
||||
// Some non-terminal node types (at the moment, I only know of
|
||||
// MODIFIERS, but there may be other such types), can be
|
||||
// leaves in the tree but not have any children. If this is
|
||||
// such a node, move on to the next sibling.
|
||||
case MODIFIERS:
|
||||
if (child.getFirstChild() == null) {
|
||||
return getBestPrintableNode(child.getNextSibling(), false);
|
||||
}
|
||||
// new jikes doesn't like fallthrough, so just duplicated here:
|
||||
return getBestPrintableNode(child, false);
|
||||
|
||||
default:
|
||||
return getBestPrintableNode(child, false);
|
||||
}
|
||||
}
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
// Because the meanings of <, >, >>, and >>> are overloaded to support
|
||||
// type arguments and type parameters, we have to treat them
|
||||
// as copyable to hidden text (or else the following syntax,
|
||||
// such as (); and what not gets lost under certain circumstances
|
||||
//
|
||||
// Since they are copied to the hidden stream, you don't want
|
||||
// to print them explicitly; they come out in the dumpHiddenXXX methods.
|
||||
// -- jdf
|
||||
private static final BitSet OTHER_COPIED_TOKENS = new BitSet() {
|
||||
{
|
||||
set(LT);
|
||||
set(GT);
|
||||
set(SR);
|
||||
set(BSR);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prints a binary operator
|
||||
*/
|
||||
private void printBinaryOperator(final AST ast) throws SketchException {
|
||||
print(ast.getFirstChild());
|
||||
if (!OTHER_COPIED_TOKENS.get(ast.getType())) {
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
}
|
||||
print(ast.getFirstChild().getNextSibling());
|
||||
}
|
||||
|
||||
private void printMethodDef(final AST ast) throws SketchException {
|
||||
final AST modifiers = ast.getFirstChild();
|
||||
final AST typeParameters, type;
|
||||
if (modifiers.getNextSibling().getType() == TYPE_PARAMETERS) {
|
||||
typeParameters = modifiers.getNextSibling();
|
||||
type = typeParameters.getNextSibling();
|
||||
} else {
|
||||
typeParameters = null;
|
||||
type = modifiers.getNextSibling();
|
||||
}
|
||||
final AST methodName = type.getNextSibling();
|
||||
// if (methodName.getText().equals("main")) {
|
||||
// pdePreprocessor.setFoundMain(true);
|
||||
// }
|
||||
pdePreprocessor.addMethod(methodName.getText());
|
||||
printChildren(ast);
|
||||
}
|
||||
|
||||
private void printIfThenElse(final AST literalIf) throws SketchException {
|
||||
out.print(literalIf.getText());
|
||||
dumpHiddenAfter(literalIf);
|
||||
|
||||
final AST condition = literalIf.getFirstChild();
|
||||
print(condition); // the "if" condition: an EXPR
|
||||
|
||||
// the "then" clause is either an SLIST or an EXPR
|
||||
final AST thenPath = condition.getNextSibling();
|
||||
print(thenPath);
|
||||
|
||||
// optional "else" clause: an SLIST or an EXPR
|
||||
// what could be simpler?
|
||||
final AST elsePath = thenPath.getNextSibling();
|
||||
if (elsePath != null) {
|
||||
out.print("else");
|
||||
final AST bestPrintableNode = getBestPrintableNode(elsePath, true);
|
||||
dumpHiddenBefore(bestPrintableNode);
|
||||
final CommonHiddenStreamToken hiddenBefore =
|
||||
((CommonASTWithHiddenTokens) elsePath).getHiddenBefore();
|
||||
if (elsePath.getType() == PdeTokenTypes.SLIST && elsePath.getNumberOfChildren() == 0 &&
|
||||
hiddenBefore == null) {
|
||||
out.print("{");
|
||||
final CommonHiddenStreamToken hiddenAfter =
|
||||
((CommonASTWithHiddenTokens) elsePath).getHiddenAfter();
|
||||
if (hiddenAfter == null) {
|
||||
out.print("}");
|
||||
} else {
|
||||
dumpHiddenTokens(hiddenAfter);
|
||||
}
|
||||
} else {
|
||||
print(elsePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the given AST. Call this function to print your PDE code.
|
||||
*
|
||||
* It works by making recursive calls to print children.
|
||||
* So the code below is one big "switch" statement on the passed AST type.
|
||||
*/
|
||||
public void print(final AST ast) throws SketchException {
|
||||
if (ast == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack.push(ast);
|
||||
|
||||
final AST child1 = ast.getFirstChild();
|
||||
AST child2 = null;
|
||||
AST child3 = null;
|
||||
if (child1 != null) {
|
||||
child2 = child1.getNextSibling();
|
||||
if (child2 != null) {
|
||||
child3 = child2.getNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
switch (ast.getType()) {
|
||||
// The top of the tree looks like this:
|
||||
// ROOT_ID "Whatever.java"
|
||||
// package
|
||||
// imports
|
||||
// class definition
|
||||
case ROOT_ID:
|
||||
dumpHiddenTokens(pdePreprocessor.getInitialHiddenToken());
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
// supporting a "package" statement in a PDE program has
|
||||
// a bunch of issues with it that need to dealt in the compilation
|
||||
// code too, so this isn't actually tested.
|
||||
case PACKAGE_DEF:
|
||||
out.print("package");
|
||||
dumpHiddenAfter(ast);
|
||||
print(ast.getFirstChild());
|
||||
break;
|
||||
|
||||
// IMPORT has exactly one child
|
||||
case IMPORT:
|
||||
out.print("import");
|
||||
dumpHiddenAfter(ast);
|
||||
print(ast.getFirstChild());
|
||||
break;
|
||||
|
||||
case STATIC_IMPORT:
|
||||
out.print("import static");
|
||||
dumpHiddenAfter(ast);
|
||||
print(ast.getFirstChild());
|
||||
break;
|
||||
|
||||
case CLASS_DEF:
|
||||
case ENUM_DEF:
|
||||
case INTERFACE_DEF:
|
||||
print(getChild(ast, MODIFIERS));
|
||||
if (ast.getType() == CLASS_DEF) {
|
||||
out.print("class");
|
||||
} else if (ast.getType() == ENUM_DEF) {
|
||||
out.print("enum");
|
||||
} else {
|
||||
out.print("interface");
|
||||
}
|
||||
dumpHiddenBefore(getChild(ast, IDENT));
|
||||
print(getChild(ast, IDENT));
|
||||
print(getChild(ast, TYPE_PARAMETERS));
|
||||
print(getChild(ast, EXTENDS_CLAUSE));
|
||||
print(getChild(ast, IMPLEMENTS_CLAUSE));
|
||||
print(getChild(ast, OBJBLOCK));
|
||||
break;
|
||||
|
||||
case EXTENDS_CLAUSE:
|
||||
if (hasChildren(ast)) {
|
||||
out.print("extends");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast, false));
|
||||
printChildren(ast);
|
||||
}
|
||||
break;
|
||||
|
||||
case IMPLEMENTS_CLAUSE:
|
||||
if (hasChildren(ast)) {
|
||||
out.print("implements");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast, false));
|
||||
printChildren(ast);
|
||||
}
|
||||
break;
|
||||
|
||||
// DOT
|
||||
case DOT:
|
||||
print(child1);
|
||||
out.print(".");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child2);
|
||||
if (child3 != null) {
|
||||
print(child3);
|
||||
}
|
||||
break;
|
||||
|
||||
case MODIFIERS:
|
||||
case OBJBLOCK:
|
||||
case CTOR_DEF:
|
||||
//case METHOD_DEF:
|
||||
case PARAMETERS:
|
||||
case PARAMETER_DEF:
|
||||
case VARIABLE_PARAMETER_DEF:
|
||||
case VARIABLE_DEF:
|
||||
case ENUM_CONSTANT_DEF:
|
||||
case TYPE:
|
||||
case SLIST:
|
||||
case ELIST:
|
||||
case ARRAY_DECLARATOR:
|
||||
case TYPECAST:
|
||||
case EXPR:
|
||||
case ARRAY_INIT:
|
||||
case FOR_INIT:
|
||||
case FOR_CONDITION:
|
||||
case FOR_ITERATOR:
|
||||
case METHOD_CALL:
|
||||
case INSTANCE_INIT:
|
||||
case INDEX_OP:
|
||||
case SUPER_CTOR_CALL:
|
||||
case CTOR_CALL:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case METHOD_DEF:
|
||||
printMethodDef(ast);
|
||||
break;
|
||||
|
||||
// if we have two children, it's of the form "a=0"
|
||||
// if just one child, it's of the form "=0" (where the
|
||||
// lhs is above this AST).
|
||||
case ASSIGN:
|
||||
if (child2 != null) {
|
||||
print(child1);
|
||||
out.print("=");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child2);
|
||||
} else {
|
||||
out.print("=");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1);
|
||||
}
|
||||
break;
|
||||
|
||||
// binary operators:
|
||||
case PLUS:
|
||||
case MINUS:
|
||||
case DIV:
|
||||
case MOD:
|
||||
case NOT_EQUAL:
|
||||
case EQUAL:
|
||||
case LE:
|
||||
case GE:
|
||||
case LOR:
|
||||
case LAND:
|
||||
case BOR:
|
||||
case BXOR:
|
||||
case BAND:
|
||||
case SL:
|
||||
case SR:
|
||||
case BSR:
|
||||
case LITERAL_instanceof:
|
||||
case PLUS_ASSIGN:
|
||||
case MINUS_ASSIGN:
|
||||
case STAR_ASSIGN:
|
||||
case DIV_ASSIGN:
|
||||
case MOD_ASSIGN:
|
||||
case SR_ASSIGN:
|
||||
case BSR_ASSIGN:
|
||||
case SL_ASSIGN:
|
||||
case BAND_ASSIGN:
|
||||
case BXOR_ASSIGN:
|
||||
case BOR_ASSIGN:
|
||||
|
||||
case LT:
|
||||
case GT:
|
||||
printBinaryOperator(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_for:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
if (child1.getType() == FOR_EACH_CLAUSE) {
|
||||
printChildren(child1);
|
||||
print(child2);
|
||||
} else {
|
||||
printChildren(ast);
|
||||
}
|
||||
break;
|
||||
|
||||
case POST_INC:
|
||||
case POST_DEC:
|
||||
print(child1);
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
// unary operators:
|
||||
case BNOT:
|
||||
case LNOT:
|
||||
case INC:
|
||||
case DEC:
|
||||
case UNARY_MINUS:
|
||||
case UNARY_PLUS:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1);
|
||||
break;
|
||||
|
||||
case LITERAL_new:
|
||||
out.print("new");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_return:
|
||||
out.print("return");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1);
|
||||
break;
|
||||
|
||||
case STATIC_INIT:
|
||||
out.print("static");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast, false));
|
||||
print(child1);
|
||||
break;
|
||||
|
||||
case LITERAL_switch:
|
||||
out.print("switch");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LABELED_STAT:
|
||||
case CASE_GROUP:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_case:
|
||||
out.print("case");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_default:
|
||||
out.print("default");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case NUM_INT:
|
||||
case CHAR_LITERAL:
|
||||
case STRING_LITERAL:
|
||||
case NUM_FLOAT:
|
||||
case NUM_LONG:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_synchronized: // 0137 to fix bug #136
|
||||
case LITERAL_assert:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_private:
|
||||
case LITERAL_public:
|
||||
case LITERAL_protected:
|
||||
case LITERAL_static:
|
||||
case LITERAL_transient:
|
||||
case LITERAL_native:
|
||||
case LITERAL_threadsafe:
|
||||
//case LITERAL_synchronized: // 0137 to fix bug #136
|
||||
case LITERAL_volatile:
|
||||
case LITERAL_class: // 0176 to fix bug #1466
|
||||
case FINAL:
|
||||
case ABSTRACT:
|
||||
case LITERAL_package:
|
||||
case LITERAL_void:
|
||||
case LITERAL_boolean:
|
||||
case LITERAL_byte:
|
||||
case LITERAL_char:
|
||||
case LITERAL_short:
|
||||
case LITERAL_int:
|
||||
case LITERAL_float:
|
||||
case LITERAL_long:
|
||||
case LITERAL_double:
|
||||
case LITERAL_true:
|
||||
case LITERAL_false:
|
||||
case LITERAL_null:
|
||||
case SEMI:
|
||||
case LITERAL_this:
|
||||
case LITERAL_super:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
case EMPTY_STAT:
|
||||
case EMPTY_FIELD:
|
||||
break;
|
||||
|
||||
case LITERAL_continue:
|
||||
case LITERAL_break:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
if (child1 != null) {// maybe label
|
||||
print(child1);
|
||||
}
|
||||
break;
|
||||
|
||||
// yuck: Distinguish between "import x.y.*" and "x = 1 * 3"
|
||||
case STAR:
|
||||
if (hasChildren(ast)) { // the binary mult. operator
|
||||
printBinaryOperator(ast);
|
||||
} else { // the special "*" in import:
|
||||
out.print("*");
|
||||
dumpHiddenAfter(ast);
|
||||
}
|
||||
break;
|
||||
|
||||
case LITERAL_throws:
|
||||
out.print("throws");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_if:
|
||||
printIfThenElse(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_while:
|
||||
out.print("while");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_do:
|
||||
out.print("do");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1); // an SLIST
|
||||
out.print("while");
|
||||
dumpHiddenBefore(getBestPrintableNode(child2, false));
|
||||
print(child2); // an EXPR
|
||||
break;
|
||||
|
||||
case LITERAL_try:
|
||||
out.print("try");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_catch:
|
||||
out.print("catch");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
// the first child is the "try" and the second is the SLIST
|
||||
case LITERAL_finally:
|
||||
out.print("finally");
|
||||
dumpHiddenAfter(ast);
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case LITERAL_throw:
|
||||
out.print("throw");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child1);
|
||||
break;
|
||||
|
||||
// the dreaded trinary operator
|
||||
case QUESTION:
|
||||
print(child1);
|
||||
out.print("?");
|
||||
dumpHiddenAfter(ast);
|
||||
print(child2);
|
||||
print(child3);
|
||||
break;
|
||||
|
||||
// pde specific or modified tokens start here
|
||||
|
||||
// Image -> BImage, Font -> BFont as appropriate
|
||||
case IDENT:
|
||||
/*
|
||||
if (ast.getText().equals("Image") &&
|
||||
Preferences.getBoolean("preproc.substitute_image")) { //, true)) {
|
||||
out.print("BImage");
|
||||
} else if (ast.getText().equals("Font") &&
|
||||
Preferences.getBoolean("preproc.substitute_font")) { //, true)) {
|
||||
out.print("BFont");
|
||||
} else {
|
||||
*/
|
||||
out.print(ast.getText());
|
||||
//}
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
// the color datatype is just an alias for int
|
||||
case LITERAL_color:
|
||||
out.print("int");
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
case WEBCOLOR_LITERAL:
|
||||
if (ast.getText().length() != 6) {
|
||||
System.err.println("Internal error: incorrect length of webcolor "
|
||||
+ "literal should have been detected sooner.");
|
||||
break;
|
||||
}
|
||||
out.print("0xff" + ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
// allow for stuff like int(43.2).
|
||||
case CONSTRUCTOR_CAST:
|
||||
final AST terminalTypeNode = child1.getFirstChild();
|
||||
final AST exprToCast = child2;
|
||||
final String pooType = terminalTypeNode.getText();
|
||||
out.print("PApplet.parse" + Character.toUpperCase(pooType.charAt(0))
|
||||
+ pooType.substring(1));
|
||||
dumpHiddenAfter(terminalTypeNode); // the left paren
|
||||
print(exprToCast);
|
||||
break;
|
||||
|
||||
// making floating point literals default to floats, not doubles
|
||||
case NUM_DOUBLE:
|
||||
final String literalDouble = ast.getText().toLowerCase();
|
||||
out.print(literalDouble);
|
||||
if (Preferences.getBoolean("preproc.substitute_floats")
|
||||
&& literalDouble.indexOf('d') == -1) { // permit literal doubles
|
||||
out.print("f");
|
||||
}
|
||||
dumpHiddenAfter(ast);
|
||||
break;
|
||||
|
||||
case TYPE_ARGUMENTS:
|
||||
case TYPE_PARAMETERS:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case TYPE_ARGUMENT:
|
||||
case TYPE_PARAMETER:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case WILDCARD_TYPE:
|
||||
out.print(ast.getText());
|
||||
dumpHiddenAfter(ast);
|
||||
print(ast.getFirstChild());
|
||||
break;
|
||||
|
||||
case TYPE_LOWER_BOUNDS:
|
||||
case TYPE_UPPER_BOUNDS:
|
||||
out.print(ast.getType() == TYPE_LOWER_BOUNDS ? "super" : "extends");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast, false));
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case ANNOTATION:
|
||||
out.print("@");
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case ANNOTATIONS:
|
||||
case ANNOTATION_ARRAY_INIT:
|
||||
printChildren(ast);
|
||||
break;
|
||||
|
||||
case ANNOTATION_MEMBER_VALUE_PAIR:
|
||||
print(ast.getFirstChild());
|
||||
out.print("=");
|
||||
dumpHiddenBefore(getBestPrintableNode(ast.getFirstChild().getNextSibling(), false));
|
||||
print(ast.getFirstChild().getNextSibling());
|
||||
break;
|
||||
|
||||
default:
|
||||
debug.println("Unrecognized type:" + ast.getType() + " ("
|
||||
+ TokenUtil.nameOf(ast) + ")");
|
||||
break;
|
||||
}
|
||||
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
}
|
||||
+1439
File diff suppressed because it is too large
Load Diff
+32
@@ -0,0 +1,32 @@
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import processing.app.SketchException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*
|
||||
*/
|
||||
public class PreprocessorResult {
|
||||
public final int headerOffset;
|
||||
public final String className;
|
||||
public final List<String> extraImports;
|
||||
public final PdePreprocessor.Mode programType;
|
||||
|
||||
public PreprocessorResult(PdePreprocessor.Mode programType,
|
||||
int headerOffset, String className,
|
||||
final List<String> extraImports) throws SketchException {
|
||||
if (className == null) {
|
||||
throw new SketchException("Could not find main class");
|
||||
}
|
||||
this.headerOffset = headerOffset;
|
||||
this.className = className;
|
||||
this.extraImports = Collections.unmodifiableList(new ArrayList<String>(extraImports));
|
||||
this.programType = programType;
|
||||
}
|
||||
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
SizeInfo - parsed elements of a size() or fullScreen() call
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2015 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import processing.app.Messages;
|
||||
import processing.core.PApplet;
|
||||
import processing.data.StringList;
|
||||
|
||||
|
||||
public class SurfaceInfo {
|
||||
StringList statements = new StringList();
|
||||
|
||||
String width;
|
||||
String height;
|
||||
String renderer;
|
||||
String path;
|
||||
|
||||
String display;
|
||||
/** null for nothing in setup(), 0 for noSmooth(), N for smooth(N) */
|
||||
//Integer quality;
|
||||
// String smooth;
|
||||
|
||||
|
||||
boolean hasOldSyntax() {
|
||||
if (width.equals("screenWidth") ||
|
||||
width.equals("screenHeight") ||
|
||||
height.equals("screenHeight") ||
|
||||
height.equals("screenWidth")) {
|
||||
final String message =
|
||||
"The screenWidth and screenHeight variables are named\n" +
|
||||
"displayWidth and displayHeight in Processing 3.\n" +
|
||||
"Or you can use the fullScreen() method instead of size().";
|
||||
Messages.showWarning("Time for a quick update", message, null);
|
||||
return true;
|
||||
}
|
||||
if (width.equals("screen.width") ||
|
||||
width.equals("screen.height") ||
|
||||
height.equals("screen.height") ||
|
||||
height.equals("screen.width")) {
|
||||
final String message =
|
||||
"The screen.width and screen.height variables are named\n" +
|
||||
"displayWidth and displayHeight in Processing 3.\n" +
|
||||
"Or you can use the fullScreen() method instead of size().";
|
||||
Messages.showWarning("Time for a quick update", message, null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
boolean hasBadSize() {
|
||||
if (!width.equals("displayWidth") &&
|
||||
!width.equals("displayHeight") &&
|
||||
PApplet.parseInt(width, -1) == -1) {
|
||||
return true;
|
||||
}
|
||||
if (!height.equals("displayWidth") &&
|
||||
!height.equals("displayHeight") &&
|
||||
PApplet.parseInt(height, -1) == -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void checkEmpty() {
|
||||
if (renderer != null) {
|
||||
if (renderer.length() == 0) { // if empty, set null
|
||||
renderer = null;
|
||||
}
|
||||
}
|
||||
if (path != null) {
|
||||
if (path.length() == 0) {
|
||||
path = null;
|
||||
}
|
||||
}
|
||||
if (display != null) {
|
||||
if (display.length() == 0) {
|
||||
display = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// public String getStatements() {
|
||||
// return statements.join(" ");
|
||||
// }
|
||||
|
||||
|
||||
public StringList getStatements() {
|
||||
return statements;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an item that will be moved from size() into the settings() method.
|
||||
* This needs to be the exact version of the statement so that it can be
|
||||
* matched against and removed from the size() method in the code.
|
||||
*/
|
||||
public void addStatement(String stmt) {
|
||||
statements.append(stmt);
|
||||
}
|
||||
|
||||
|
||||
public void addStatements(StringList list) {
|
||||
statements.append(list);
|
||||
}
|
||||
|
||||
|
||||
/** @return true if there's code to be inserted for a settings() method. */
|
||||
public boolean hasSettings() {
|
||||
return statements.size() != 0;
|
||||
}
|
||||
|
||||
|
||||
/** @return the contents of the settings() method to be inserted */
|
||||
public String getSettings() {
|
||||
return statements.join(" ");
|
||||
}
|
||||
|
||||
|
||||
// Added for Android Mode to check whether OpenGL is in use
|
||||
// https://github.com/processing/processing/issues/4441
|
||||
/**
|
||||
* Return the renderer specified (null if none specified).
|
||||
* @since 3.2.2
|
||||
*/
|
||||
public String getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package processing.mode.java.preproc;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import antlr.collections.AST;
|
||||
import processing.mode.java.preproc.PdeTokenTypes;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*
|
||||
*/
|
||||
public class TokenUtil {
|
||||
private static final String[] tokenNames= new String[200];
|
||||
static {
|
||||
for (int i = 0; i < tokenNames.length; i++) {
|
||||
tokenNames[i] = "ERROR:" + i;
|
||||
}
|
||||
for (final Field f : PdeTokenTypes.class.getDeclaredFields()) {
|
||||
try {
|
||||
tokenNames[f.getInt(null)] = f.getName();
|
||||
} catch (Exception unexpected) {
|
||||
throw new RuntimeException(unexpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String nameOf(final AST node) {
|
||||
return tokenNames[node.getType()];
|
||||
}
|
||||
}
|
||||
+1832
File diff suppressed because it is too large
Load Diff
+394
@@ -0,0 +1,394 @@
|
||||
/* -*- mode: antlr; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
header {
|
||||
package processing.mode.java.preproc;
|
||||
}
|
||||
|
||||
class PdeRecognizer extends JavaRecognizer;
|
||||
|
||||
options {
|
||||
importVocab = Java;
|
||||
exportVocab = PdePartial;
|
||||
|
||||
//codeGenMakeSwitchThreshold=10; // this is set high for debugging
|
||||
//codeGenBitsetTestThreshold=10; // this is set high for debugging
|
||||
|
||||
// developers may to want to set this to true for better
|
||||
// debugging messages, however, doing so disables highlighting errors
|
||||
// in the editor.
|
||||
defaultErrorHandler = false; //true;
|
||||
}
|
||||
|
||||
tokens {
|
||||
CONSTRUCTOR_CAST; EMPTY_FIELD;
|
||||
}
|
||||
|
||||
{
|
||||
// this clause copied from java15.g! ANTLR does not copy this
|
||||
// section from the super grammar.
|
||||
/**
|
||||
* Counts the number of LT seen in the typeArguments production.
|
||||
* It is used in semantic predicates to ensure we have seen
|
||||
* enough closing '>' characters; which actually may have been
|
||||
* either GT, SR or BSR tokens.
|
||||
*/
|
||||
private int ltCounter = 0;
|
||||
|
||||
private PdePreprocessor pp;
|
||||
public PdeRecognizer(final PdePreprocessor pp, final TokenStream ts) {
|
||||
this(ts);
|
||||
this.pp = pp;
|
||||
}
|
||||
|
||||
private void mixed() throws RecognitionException, TokenStreamException {
|
||||
throw new RecognitionException("It looks like you're mixing \"active\" and \"static\" modes.",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}
|
||||
}
|
||||
|
||||
pdeProgram
|
||||
:
|
||||
// Some programs can be equally well interpreted as STATIC or ACTIVE;
|
||||
// this forces the parser to prefer the STATIC interpretation.
|
||||
(staticProgram) => staticProgram
|
||||
{ pp.setMode(PdePreprocessor.Mode.STATIC); }
|
||||
|
||||
| (activeProgram) => activeProgram
|
||||
{ pp.setMode(PdePreprocessor.Mode.ACTIVE); }
|
||||
|
||||
| staticProgram
|
||||
{ pp.setMode(PdePreprocessor.Mode.STATIC); }
|
||||
;
|
||||
|
||||
// advanced mode is really just a normal java file
|
||||
javaProgram
|
||||
: compilationUnit
|
||||
;
|
||||
|
||||
activeProgram
|
||||
: (
|
||||
(IDENT LPAREN) => IDENT LPAREN { mixed(); }
|
||||
| possiblyEmptyField
|
||||
)+ EOF!
|
||||
;
|
||||
|
||||
staticProgram
|
||||
: (
|
||||
statement
|
||||
)* EOF!
|
||||
;
|
||||
|
||||
// copy of the java.g rule with WEBCOLOR_LITERAL added
|
||||
constant
|
||||
: NUM_INT
|
||||
| CHAR_LITERAL
|
||||
| STRING_LITERAL
|
||||
| NUM_FLOAT
|
||||
| NUM_LONG
|
||||
| NUM_DOUBLE
|
||||
| webcolor_literal
|
||||
;
|
||||
|
||||
// fix bug http://dev.processing.org/bugs/show_bug.cgi?id=1519
|
||||
// by altering a syntactic predicate whose sole purpose is to
|
||||
// emit a useless error with no line numbers.
|
||||
// These are from Java15.g, with a few lines edited to make nice errors.
|
||||
|
||||
// Type arguments to a class or interface type
|
||||
typeArguments
|
||||
{int currentLtLevel = 0;}
|
||||
:
|
||||
{currentLtLevel = ltCounter;}
|
||||
LT! {ltCounter++;}
|
||||
typeArgument
|
||||
(options{greedy=true;}: // match as many as possible
|
||||
{if (! (inputState.guessing !=0 || ltCounter == currentLtLevel + 1)) {
|
||||
throw new RecognitionException("Maybe too many > characters?",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}}
|
||||
COMMA! typeArgument
|
||||
)*
|
||||
|
||||
( // turn warning off since Antlr generates the right code,
|
||||
// plus we have our semantic predicate below
|
||||
options{generateAmbigWarnings=false;}:
|
||||
typeArgumentsOrParametersEnd
|
||||
)?
|
||||
|
||||
// make sure we have gobbled up enough '>' characters
|
||||
// if we are at the "top level" of nested typeArgument productions
|
||||
{if (! ((currentLtLevel != 0) || ltCounter == currentLtLevel)) {
|
||||
throw new RecognitionException("Maybe too many > characters?",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}}
|
||||
|
||||
{#typeArguments = #(#[TYPE_ARGUMENTS, "TYPE_ARGUMENTS"], #typeArguments);}
|
||||
;
|
||||
|
||||
typeParameters
|
||||
{int currentLtLevel = 0;}
|
||||
:
|
||||
{currentLtLevel = ltCounter;}
|
||||
LT! {ltCounter++;}
|
||||
typeParameter (COMMA! typeParameter)*
|
||||
(typeArgumentsOrParametersEnd)?
|
||||
|
||||
// make sure we have gobbled up enough '>' characters
|
||||
// if we are at the "top level" of nested typeArgument productions
|
||||
{if (! ((currentLtLevel != 0) || ltCounter == currentLtLevel)) {
|
||||
throw new RecognitionException("Maybe too many > characters?",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}}
|
||||
|
||||
{#typeParameters = #(#[TYPE_PARAMETERS, "TYPE_PARAMETERS"], #typeParameters);}
|
||||
;
|
||||
|
||||
|
||||
// this gobbles up *some* amount of '>' characters, and counts how many
|
||||
// it gobbled.
|
||||
protected typeArgumentsOrParametersEnd
|
||||
: GT! {ltCounter-=1;}
|
||||
| SR! {ltCounter-=2;}
|
||||
| BSR! {ltCounter-=3;}
|
||||
;
|
||||
|
||||
// of the form #cc008f in PDE
|
||||
webcolor_literal
|
||||
: w:WEBCOLOR_LITERAL
|
||||
{ if (! (processing.app.Preferences.getBoolean("preproc.web_colors")
|
||||
&&
|
||||
w.getText().length() == 6)) {
|
||||
throw new RecognitionException("Web colors must be exactly 6 hex digits. This looks like " + w.getText().length() + ".",
|
||||
getFilename(), LT(1).getLine(), LT(1).getColumn());
|
||||
}} // must be exactly 6 hex digits
|
||||
;
|
||||
|
||||
// copy of the java.g builtInType rule
|
||||
builtInConsCastType
|
||||
: "void"
|
||||
| "boolean"
|
||||
| "byte"
|
||||
| "char"
|
||||
| "short"
|
||||
| "int"
|
||||
| "float"
|
||||
| "long"
|
||||
| "double"
|
||||
;
|
||||
|
||||
// our types include the java types and "color". this is separated into two
|
||||
// rules so that constructor casts can just use the original typelist, since
|
||||
// we don't want to support the color type as a constructor cast.
|
||||
//
|
||||
builtInType
|
||||
: builtInConsCastType
|
||||
| "color" // aliased to an int in PDE
|
||||
{ processing.app.Preferences.getBoolean("preproc.color_datatype") }?
|
||||
;
|
||||
|
||||
// constructor style casts.
|
||||
constructorCast!
|
||||
: t:consCastTypeSpec[true]
|
||||
LPAREN!
|
||||
e:expression
|
||||
RPAREN!
|
||||
// if this is a string literal, make sure the type we're trying to cast
|
||||
// to is one of the supported ones
|
||||
//
|
||||
{ (#e == null) ||
|
||||
( (#e.getType() != STRING_LITERAL) ||
|
||||
( #t.getType() == LITERAL_boolean ||
|
||||
#t.getType() == LITERAL_double ||
|
||||
#t.getType() == LITERAL_float ||
|
||||
#t.getType() == LITERAL_int ||
|
||||
#t.getType() == LITERAL_long ||
|
||||
#t.getType() == LITERAL_short )) }?
|
||||
// create the node
|
||||
//
|
||||
{#constructorCast = #(#[CONSTRUCTOR_CAST,"CONSTRUCTOR_CAST"], t, e);}
|
||||
;
|
||||
|
||||
// A list of types that be used as the destination type in a constructor-style
|
||||
// cast. Ideally, this would include all class types, not just "String".
|
||||
// Unfortunately, it's not possible to tell whether Foo(5) is supposed to be
|
||||
// a method call or a constructor cast without have a table of all valid
|
||||
// types or methods, which requires semantic analysis (eg processing of import
|
||||
// statements). So we accept the set of built-in types plus "String".
|
||||
//
|
||||
consCastTypeSpec[boolean addImagNode]
|
||||
// : stringTypeSpec[addImagNode]
|
||||
// | builtInConsCastTypeSpec[addImagNode]
|
||||
: builtInConsCastTypeSpec[addImagNode]
|
||||
// trying to remove String() cast [fry]
|
||||
;
|
||||
|
||||
//stringTypeSpec[boolean addImagNode]
|
||||
// : id:IDENT { #id.getText().equals("String") }?
|
||||
// {
|
||||
// if ( addImagNode ) {
|
||||
// #stringTypeSpec = #(#[TYPE,"TYPE"],
|
||||
// #stringTypeSpec);
|
||||
// }
|
||||
// }
|
||||
// ;
|
||||
|
||||
builtInConsCastTypeSpec[boolean addImagNode]
|
||||
: builtInConsCastType
|
||||
{
|
||||
if ( addImagNode ) {
|
||||
#builtInConsCastTypeSpec = #(#[TYPE,"TYPE"],
|
||||
#builtInConsCastTypeSpec);
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
// Since "color" tokens are lexed as LITERAL_color now, we need to have a rule
|
||||
// that can generate a method call from an expression that starts with this
|
||||
// token
|
||||
//
|
||||
colorMethodCall
|
||||
: c:"color" {#c.setType(IDENT);} // this would default to LITERAL_color
|
||||
lp:LPAREN^ {#lp.setType(METHOD_CALL);}
|
||||
argList
|
||||
RPAREN!
|
||||
;
|
||||
|
||||
// copy of the java.g rule with added constructorCast and colorMethodCall
|
||||
// alternatives
|
||||
primaryExpression
|
||||
: (consCastTypeSpec[false] LPAREN) => constructorCast
|
||||
{ processing.app.Preferences.getBoolean("preproc.enhanced_casting") }?
|
||||
| identPrimary ( options {greedy=true;} : DOT^ "class" )?
|
||||
| constant
|
||||
| "true"
|
||||
| "false"
|
||||
| "null"
|
||||
| newExpression
|
||||
| "this"
|
||||
| "super"
|
||||
| LPAREN! assignmentExpression RPAREN!
|
||||
| colorMethodCall
|
||||
// look for int.class and int[].class
|
||||
| builtInType
|
||||
( lbt:LBRACK^ {#lbt.setType(ARRAY_DECLARATOR);} RBRACK! )*
|
||||
DOT^ "class"
|
||||
;
|
||||
|
||||
// the below variable rule hacks are needed so that it's possible for the
|
||||
// emitter to correctly output variable declarations of the form "float a, b"
|
||||
// from the AST. This means that our AST has a somewhat different form in
|
||||
// these rules than the java one does, and this new form may have its own
|
||||
// semantic issues. But it seems to fix the comma declaration issues.
|
||||
//
|
||||
variableDefinitions![AST mods, AST t]
|
||||
: vd:variableDeclarator[getASTFactory().dupTree(mods),
|
||||
getASTFactory().dupTree(t)]
|
||||
{#variableDefinitions = #(#[VARIABLE_DEF,"VARIABLE_DEF"], mods,
|
||||
t, vd);}
|
||||
;
|
||||
variableDeclarator[AST mods, AST t]
|
||||
: ( id:IDENT (lb:LBRACK^ {#lb.setType(ARRAY_DECLARATOR);} RBRACK!)*
|
||||
v:varInitializer (COMMA!)? )+
|
||||
;
|
||||
|
||||
// java.g builds syntax trees with an inconsistent structure. override one of
|
||||
// the rules there to fix this.
|
||||
//
|
||||
explicitConstructorInvocation!
|
||||
: (typeArguments)?
|
||||
t:"this" LPAREN a1:argList RPAREN SEMI
|
||||
{#explicitConstructorInvocation = #(#[CTOR_CALL, "CTOR_CALL"],
|
||||
#t, #a1);}
|
||||
| s:"super" LPAREN a2:argList RPAREN SEMI
|
||||
{#explicitConstructorInvocation = #(#[SUPER_CTOR_CALL,
|
||||
"SUPER_CTOR_CALL"],
|
||||
#s, #a2);}
|
||||
;
|
||||
|
||||
// quick-n-dirty hack to the get the advanced class name. we should
|
||||
// really be getting it from the AST and not forking this rule from
|
||||
// the java.g copy at all. Since this is a recursive descent parser, we get
|
||||
// the last class name in the file so that we don't end up with the classname
|
||||
// of an inner class. If there is more than one "outer" class in a file,
|
||||
// this heuristic will fail.
|
||||
//
|
||||
classDefinition![AST modifiers]
|
||||
: "class" i:IDENT
|
||||
// it _might_ have type paramaters
|
||||
(tp:typeParameters)?
|
||||
// it _might_ have a superclass...
|
||||
sc:superClassClause
|
||||
// it might implement some interfaces...
|
||||
ic:implementsClause
|
||||
// now parse the body of the class
|
||||
cb:classBlock
|
||||
{#classDefinition = #(#[CLASS_DEF,"CLASS_DEF"],
|
||||
modifiers,i,tp,sc,ic,cb);
|
||||
pp.setAdvClassName(i.getText());}
|
||||
;
|
||||
|
||||
possiblyEmptyField
|
||||
: classField
|
||||
| s:SEMI {#s.setType(EMPTY_FIELD);}
|
||||
;
|
||||
|
||||
class PdeLexer extends JavaLexer;
|
||||
|
||||
options {
|
||||
importVocab=PdePartial;
|
||||
exportVocab=Pde;
|
||||
}
|
||||
|
||||
// We need to preserve whitespace and commentary instead of ignoring
|
||||
// like the supergrammar does. Otherwise Jikes won't be able to give
|
||||
// us error messages that point to the equivalent PDE code.
|
||||
|
||||
// WS, SL_COMMENT, ML_COMMENT are copies of the original productions,
|
||||
// but with the SKIP assigment removed.
|
||||
|
||||
WS : ( ' '
|
||||
| '\t'
|
||||
| '\f'
|
||||
// handle newlines
|
||||
| ( options {generateAmbigWarnings=false;}
|
||||
: "\r\n" // Evil DOS
|
||||
| '\r' // Macintosh
|
||||
| '\n' // Unix (the right way)
|
||||
)
|
||||
{ newline(); }
|
||||
)+
|
||||
;
|
||||
|
||||
// Single-line comments
|
||||
SL_COMMENT
|
||||
: "//"
|
||||
(~('\n'|'\r'))* ('\n'|'\r'('\n')?)
|
||||
{newline();}
|
||||
;
|
||||
|
||||
// multiple-line comments
|
||||
ML_COMMENT
|
||||
: "/*"
|
||||
( /* '\r' '\n' can be matched in one alternative or by matching
|
||||
'\r' in one iteration and '\n' in another. I am trying to
|
||||
handle any flavor of newline that comes in, but the language
|
||||
that allows both "\r\n" and "\r" and "\n" to all be valid
|
||||
newline is ambiguous. Consequently, the resulting grammar
|
||||
must be ambiguous. I'm shutting this warning off.
|
||||
*/
|
||||
options {
|
||||
generateAmbigWarnings=false;
|
||||
}
|
||||
:
|
||||
{ LA(2)!='/' }? '*'
|
||||
| '\r' '\n' {newline();}
|
||||
| '\r' {newline();}
|
||||
| '\n' {newline();}
|
||||
| ~('*'|'\n'|'\r')
|
||||
)*
|
||||
"*/"
|
||||
;
|
||||
|
||||
WEBCOLOR_LITERAL
|
||||
: '#'! (HEX_DIGIT)+
|
||||
;
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-06 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.runner;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for dealing with parser/compiler output.
|
||||
* <P>
|
||||
* Different instances of MessageStream need to do different things with
|
||||
* messages. In particular, a stream instance used for parsing output from
|
||||
* the compiler compiler has to interpret its messages differently than one
|
||||
* parsing output from the runtime.
|
||||
* <P>
|
||||
* Classes which consume messages and do something with them
|
||||
* should implement this interface.
|
||||
*/
|
||||
public interface MessageConsumer {
|
||||
|
||||
public void message(String s);
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-06 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.runner;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/**
|
||||
* Slurps up messages from compiler.
|
||||
*/
|
||||
public class MessageSiphon implements Runnable {
|
||||
BufferedReader streamReader;
|
||||
Thread thread;
|
||||
MessageConsumer consumer;
|
||||
|
||||
|
||||
public MessageSiphon(InputStream stream, MessageConsumer consumer) {
|
||||
this.streamReader = new BufferedReader(new InputStreamReader(stream));
|
||||
this.consumer = consumer;
|
||||
|
||||
thread = new Thread(this);
|
||||
// don't set priority too low, otherwise exceptions won't
|
||||
// bubble up in time (i.e. compile errors have a weird delay)
|
||||
//thread.setPriority(Thread.MIN_PRIORITY);
|
||||
thread.setPriority(Thread.MAX_PRIORITY-1);
|
||||
//thread.start();
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
// process data until we hit EOF; this will happily block
|
||||
// (effectively sleeping the thread) until new data comes in.
|
||||
// when the program is finally done, null will come through.
|
||||
//
|
||||
String currentLine;
|
||||
while ((currentLine = streamReader.readLine()) != null) {
|
||||
// \n is added again because readLine() strips it out
|
||||
//EditorConsole.systemOut.println("messaging in");
|
||||
consumer.message(currentLine + "\n");
|
||||
//EditorConsole.systemOut.println("messaging out");
|
||||
}
|
||||
//EditorConsole.systemOut.println("messaging thread done");
|
||||
thread = null;
|
||||
|
||||
} catch (NullPointerException npe) {
|
||||
// Fairly common exception during shutdown
|
||||
thread = null;
|
||||
|
||||
} catch (Exception e) {
|
||||
// On Linux and sometimes on Mac OS X, a "bad file descriptor"
|
||||
// message comes up when closing an applet that's run externally.
|
||||
// That message just gets supressed here..
|
||||
String mess = e.getMessage();
|
||||
if ((mess != null) &&
|
||||
(mess.indexOf("Bad file descriptor") != -1)) {
|
||||
//if (e.getMessage().indexOf("Bad file descriptor") == -1) {
|
||||
//System.err.println("MessageSiphon err " + e);
|
||||
//e.printStackTrace();
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Thread getThread() {
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-08 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.runner;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/**
|
||||
* OutputStream to handle stdout/stderr messages.
|
||||
* <P>
|
||||
* This is used by Editor, System.err is set to
|
||||
* new PrintStream(new MessageStream()).
|
||||
* It's also used by Compiler.
|
||||
*/
|
||||
class MessageStream extends OutputStream {
|
||||
|
||||
MessageConsumer messageConsumer;
|
||||
|
||||
public MessageStream(MessageConsumer messageConsumer) {
|
||||
this.messageConsumer = messageConsumer;
|
||||
}
|
||||
|
||||
public void close() { }
|
||||
|
||||
public void flush() { }
|
||||
|
||||
public void write(byte b[]) {
|
||||
// this never seems to get called
|
||||
System.out.println("leech1: " + new String(b));
|
||||
}
|
||||
|
||||
public void write(byte b[], int offset, int length) {
|
||||
//System.out.println("leech2: " + new String(b));
|
||||
this.messageConsumer.message(new String(b, offset, length));
|
||||
}
|
||||
|
||||
public void write(int b) {
|
||||
// this never seems to get called
|
||||
System.out.println("leech3: '" + ((char)b) + "'");
|
||||
}
|
||||
}
|
||||
+953
@@ -0,0 +1,953 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2004-13 Ben Fry and Casey Reas
|
||||
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.mode.java.runner;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.app.exec.StreamRedirectThread;
|
||||
import processing.app.ui.Toolkit;
|
||||
import processing.core.*;
|
||||
import processing.data.StringList;
|
||||
import processing.mode.java.JavaBuild;
|
||||
import processing.mode.java.JavaEditor;
|
||||
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Point;
|
||||
import java.io.*;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.*;
|
||||
|
||||
import com.sun.jdi.*;
|
||||
import com.sun.jdi.connect.*;
|
||||
import com.sun.jdi.connect.Connector.Argument;
|
||||
import com.sun.jdi.event.*;
|
||||
import com.sun.jdi.request.*;
|
||||
|
||||
|
||||
/**
|
||||
* Runs a compiled sketch. As of release 0136, all sketches are run externally
|
||||
* to the environment so that a debugging interface can be used. This opens up
|
||||
* future options for a decent debugger, but in the meantime fixes several
|
||||
* problems with output and error streams, messages getting lost on Mac OS X,
|
||||
* the run/stop buttons not working, libraries not shutting down, exceptions
|
||||
* not coming through, exceptions being printed twice, having to force quit
|
||||
* if you make a bad while() loop, and so on.
|
||||
*/
|
||||
public class Runner implements MessageConsumer {
|
||||
// private boolean presenting;
|
||||
|
||||
// Object that listens for error messages or exceptions.
|
||||
protected RunnerListener listener;
|
||||
|
||||
// Running remote VM
|
||||
protected volatile VirtualMachine vm;
|
||||
protected boolean vmReturnedError;
|
||||
|
||||
// Thread transferring remote error stream to our error stream
|
||||
protected Thread errThread = null;
|
||||
|
||||
// Thread transferring remote output stream to our output stream
|
||||
protected Thread outThread = null;
|
||||
|
||||
protected SketchException exception;
|
||||
protected JavaEditor editor;
|
||||
protected JavaBuild build;
|
||||
protected Process process;
|
||||
|
||||
protected PrintStream sketchErr;
|
||||
protected PrintStream sketchOut;
|
||||
|
||||
protected volatile boolean cancelled;
|
||||
protected final Object cancelLock = new Object[0];
|
||||
|
||||
|
||||
public Runner(JavaBuild build, RunnerListener listener) throws SketchException {
|
||||
this.listener = listener;
|
||||
// this.sketch = sketch;
|
||||
this.build = build;
|
||||
|
||||
checkLocalHost();
|
||||
|
||||
if (listener instanceof JavaEditor) {
|
||||
this.editor = (JavaEditor) listener;
|
||||
sketchErr = editor.getConsole().getErr();
|
||||
sketchOut = editor.getConsole().getOut();
|
||||
} else {
|
||||
sketchErr = System.err;
|
||||
sketchOut = System.out;
|
||||
}
|
||||
|
||||
// Make sure all the imported libraries will actually run with this setup.
|
||||
int bits = Platform.getNativeBits();
|
||||
String variant = Platform.getVariant();
|
||||
|
||||
for (Library library : build.getImportedLibraries()) {
|
||||
if (!library.supportsArch(PApplet.platform, variant)) {
|
||||
sketchErr.println(library.getName() + " does not run on this architecture: " + variant);
|
||||
int opposite = (bits == 32) ? 64 : 32;
|
||||
if (Platform.isMacOS()) {
|
||||
//if (library.supportsArch(PConstants.MACOSX, opposite)) { // should always be true
|
||||
throw new SketchException("To use " + library.getName() + ", " +
|
||||
"switch to " + opposite + "-bit mode in Preferences.");
|
||||
//}
|
||||
} else {
|
||||
throw new SketchException(library.getName() + " is only compatible " +
|
||||
"with the " + opposite + "-bit download of Processing.");
|
||||
//throw new SketchException(library.getName() + " does not run in " + bits + "-bit mode.");
|
||||
// "To use this library, switch to 32-bit mode in Preferences." (OS X)
|
||||
// "To use this library, you must use the 32-bit version of Processing."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Has the user screwed up their hosts file?
|
||||
* https://github.com/processing/processing/issues/4738
|
||||
*/
|
||||
static private void checkLocalHost() throws SketchException {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName("localhost");
|
||||
if (!address.getHostAddress().equals("127.0.0.1")) {
|
||||
System.err.println("Your computer is not properly mapping 'localhost' to '127.0.0.1',");
|
||||
System.err.println("which prevents sketches from working properly because 'localhost'");
|
||||
System.err.println("is needed to connect the PDE to your sketch while it's running.");
|
||||
System.err.println("If you don't recall making this change, or know how to fix it:");
|
||||
System.err.println("https://www.google.com/search?q=add+localhost+to+hosts+file+" + Platform.getName());
|
||||
throw new SketchException("Cannot run due to changes in your 'hosts' file. " +
|
||||
"See the console for details.", false);
|
||||
}
|
||||
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public VirtualMachine launch(String[] args) {
|
||||
if (launchVirtualMachine(false, args)) {
|
||||
generateTrace();
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
|
||||
public VirtualMachine present(String[] args) {
|
||||
if (launchVirtualMachine(true, args)) {
|
||||
generateTrace();
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether the last invocation of launchJava() was successful or not
|
||||
*/
|
||||
public boolean vmReturnedError() {
|
||||
return vmReturnedError;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple non-blocking launch of the virtual machine. VM starts suspended.
|
||||
* @return debuggee VM or null on failure
|
||||
*/
|
||||
public VirtualMachine debug(String[] args) {
|
||||
if (launchVirtualMachine(false, args)) { // will return null on failure
|
||||
redirectStreams(vm);
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Redirect a VMs output and error streams to System.out and System.err
|
||||
*/
|
||||
protected void redirectStreams(VirtualMachine vm) {
|
||||
MessageSiphon ms = new MessageSiphon(process.getErrorStream(), this);
|
||||
errThread = ms.getThread();
|
||||
outThread = new StreamRedirectThread("VM output reader", process.getInputStream(), System.out);
|
||||
errThread.start();
|
||||
outThread.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Additional access to the virtual machine. TODO: may not be needed
|
||||
* @return debugge VM or null if not running
|
||||
*/
|
||||
public VirtualMachine vm() {
|
||||
return vm;
|
||||
}
|
||||
|
||||
|
||||
public boolean launchVirtualMachine(boolean present, String[] args) {
|
||||
StringList vmParams = getMachineParams();
|
||||
StringList sketchParams = getSketchParams(present, args);
|
||||
// PApplet.printArray(sketchParams);
|
||||
int port = 8000 + (int) (Math.random() * 1000);
|
||||
String portStr = String.valueOf(port);
|
||||
|
||||
// Added 'quiet=y' for 3.0.2 to prevent command line parsing problems
|
||||
// https://github.com/processing/processing/issues/4098
|
||||
String jdwpArg = "-agentlib:jdwp=transport=dt_socket,address=" + portStr + ",server=y,suspend=y,quiet=y";
|
||||
|
||||
// Everyone works the same under Java 7 (also on OS X)
|
||||
StringList commandArgs = new StringList();
|
||||
commandArgs.append(Platform.getJavaPath());
|
||||
commandArgs.append(jdwpArg);
|
||||
|
||||
commandArgs.append(vmParams);
|
||||
commandArgs.append(sketchParams);
|
||||
|
||||
// Opportunistically quit if the launch was cancelled,
|
||||
// the next chance to cancel will be after connecting to the VM
|
||||
if (cancelled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
launchJava(commandArgs.array());
|
||||
|
||||
AttachingConnector connector = (AttachingConnector)
|
||||
findConnector("com.sun.jdi.SocketAttach");
|
||||
//PApplet.println(connector); // gets the defaults
|
||||
|
||||
Map<String, Argument> arguments = connector.defaultArguments();
|
||||
|
||||
// Connector.Argument addressArg =
|
||||
// (Connector.Argument)arguments.get("address");
|
||||
// addressArg.setValue(addr);
|
||||
Connector.Argument portArg = arguments.get("port");
|
||||
portArg.setValue(portStr);
|
||||
|
||||
// Connector.Argument timeoutArg =
|
||||
// (Connector.Argument)arguments.get("timeout");
|
||||
// timeoutArg.setValue("10000");
|
||||
|
||||
//PApplet.println(connector); // prints the current
|
||||
//com.sun.tools.jdi.AbstractLauncher al;
|
||||
//com.sun.tools.jdi.RawCommandLineLauncher rcll;
|
||||
|
||||
//System.out.println(PApplet.javaVersion);
|
||||
// http://java.sun.com/j2se/1.5.0/docs/guide/jpda/conninv.html#sunlaunch
|
||||
|
||||
try {
|
||||
// boolean available = false;
|
||||
// while (!available) {
|
||||
while (true) {
|
||||
try {
|
||||
Messages.log(getClass().getName() + " attempting to attach to VM");
|
||||
synchronized (cancelLock) {
|
||||
vm = connector.attach(arguments);
|
||||
if (cancelled && vm != null) {
|
||||
// cancelled and connected to the VM, handle closing now
|
||||
Messages.log(getClass().getName() + " aborting, launch cancelled");
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// vm = connector.attach(arguments);
|
||||
if (vm != null) {
|
||||
Messages.log(getClass().getName() + " attached to the VM");
|
||||
// generateTrace();
|
||||
// available = true;
|
||||
return true;
|
||||
}
|
||||
} catch (ConnectException ce) {
|
||||
// This will fire ConnectException (socket not available) until
|
||||
// the VM finishes starting up and opens its socket for us.
|
||||
Messages.log(getClass().getName() + " socket for VM not ready");
|
||||
// System.out.println("waiting");
|
||||
// e.printStackTrace();
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ie) {
|
||||
Messages.loge(getClass().getName() + " interrupted", ie);
|
||||
// ie.printStackTrace(sketchErr);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Messages.loge(getClass().getName() + " while attaching to VM", e);
|
||||
}
|
||||
}
|
||||
// } catch (IOException exc) {
|
||||
// throw new Error("Unable to launch target VM: " + exc);
|
||||
} catch (IllegalConnectorArgumentsException exc) {
|
||||
throw new Error("Internal error: " + exc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected StringList getMachineParams() {
|
||||
StringList params = new StringList();
|
||||
|
||||
//params.add("-Xint"); // interpreted mode
|
||||
//params.add("-Xprof"); // profiler
|
||||
//params.add("-Xaprof"); // allocation profiler
|
||||
//params.add("-Xrunhprof:cpu=samples"); // old-style profiler
|
||||
|
||||
// TODO change this to use run.args = true, run.args.0, run.args.1, etc.
|
||||
// so that spaces can be included in the arg names
|
||||
String options = Preferences.get("run.options");
|
||||
if (options.length() > 0) {
|
||||
String pieces[] = PApplet.split(options, ' ');
|
||||
for (int i = 0; i < pieces.length; i++) {
|
||||
String p = pieces[i].trim();
|
||||
if (p.length() > 0) {
|
||||
params.append(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Preferences.getBoolean("run.options.memory")) {
|
||||
params.append("-Xms" + Preferences.get("run.options.memory.initial") + "m");
|
||||
params.append("-Xmx" + Preferences.get("run.options.memory.maximum") + "m");
|
||||
}
|
||||
|
||||
// Surprised this wasn't here before; added for 3.2.1
|
||||
params.append("-Djna.nosys=true");
|
||||
|
||||
// Added for 3.2.1, was still using the default ext.dirs in the PDE
|
||||
try {
|
||||
String extPath =
|
||||
new File(Platform.getJavaHome(), "lib/ext").getCanonicalPath();
|
||||
// quoting this on OS X causes it to fail
|
||||
//params.append("-Djava.ext.dirs=\"" + extPath + "\"");
|
||||
params.append("-Djava.ext.dirs=" + extPath);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (Platform.isMacOS()) {
|
||||
// This successfully sets the application menu name,
|
||||
// but somehow, not the dock name itself.
|
||||
params.append("-Xdock:name=" + build.getSketchClassName());
|
||||
// No longer needed / doesn't seem to do anything differently
|
||||
//params.append("-Dcom.apple.mrj.application.apple.menu.about.name=" +
|
||||
// build.getSketchClassName());
|
||||
}
|
||||
// sketch.libraryPath might be ""
|
||||
// librariesClassPath will always have sep char prepended
|
||||
params.append("-Djava.library.path=" +
|
||||
build.getJavaLibraryPath() +
|
||||
File.pathSeparator +
|
||||
System.getProperty("java.library.path"));
|
||||
|
||||
params.append("-cp");
|
||||
params.append(build.getClassPath());
|
||||
|
||||
// enable assertions
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=1188
|
||||
params.append("-ea");
|
||||
//PApplet.println(PApplet.split(sketch.classPath, ':'));
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
protected StringList getSketchParams(boolean present, String[] args) {
|
||||
StringList params = new StringList();
|
||||
|
||||
// It's dangerous to add your own main() to your code,
|
||||
// but if you've done it, we'll respect your right to hang yourself.
|
||||
// http://processing.org/bugs/bugzilla/1446.html
|
||||
if (build.getFoundMain()) {
|
||||
params.append(build.getSketchClassName());
|
||||
|
||||
} else {
|
||||
params.append("processing.core.PApplet");
|
||||
|
||||
// Get the stored device index (starts at 1)
|
||||
// By default, set to -1, meaning 'the default display',
|
||||
// which is the same display as the one being used by the Editor.
|
||||
int runDisplay = Preferences.getInteger("run.display");
|
||||
|
||||
// If there was a saved location (this guy has been run more than once)
|
||||
// then the location will be set to the last position of the sketch window.
|
||||
// This will be passed to the PApplet runner using something like
|
||||
// --location=30,20
|
||||
// Otherwise, the editor location will be passed, and the applet will
|
||||
// figure out where to place itself based on the editor location.
|
||||
// --editor-location=150,20
|
||||
if (editor != null) { // if running processing-cmd, don't do placement
|
||||
GraphicsDevice editorDevice =
|
||||
editor.getGraphicsConfiguration().getDevice();
|
||||
GraphicsEnvironment ge =
|
||||
GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
GraphicsDevice[] devices = ge.getScreenDevices();
|
||||
|
||||
// Make sure the display set in Preferences actually exists
|
||||
GraphicsDevice runDevice = editorDevice;
|
||||
if (runDisplay > 0 && runDisplay <= devices.length) {
|
||||
runDevice = devices[runDisplay-1];
|
||||
} else {
|
||||
// If a bad display (or -1 display) is selected, use the same display as the editor
|
||||
if (runDisplay > 0) { // don't complain about -1 or 0
|
||||
System.err.println("Display " + runDisplay + " not available.");
|
||||
}
|
||||
runDevice = editorDevice;
|
||||
for (int i = 0; i < devices.length; i++) {
|
||||
if (devices[i] == runDevice) {
|
||||
// Prevent message on the first run
|
||||
if (runDisplay != -1) {
|
||||
System.err.println("Setting 'Run Sketches on Display' preference to display " + (i+1));
|
||||
}
|
||||
runDisplay = i + 1;
|
||||
// Wasn't setting the pref to avoid screwing things up with
|
||||
// something temporary. But not setting it makes debugging one's
|
||||
// setup just too damn weird, so changing that behavior.
|
||||
Preferences.setInteger("run.display", runDisplay);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point windowLocation = editor.getSketchLocation();
|
||||
// if (windowLocation != null) {
|
||||
// // could check to make sure the sketch location is on the device
|
||||
// // that's specified in Preferences, but that's going to be annoying
|
||||
// // if you move a sketch to another window, then it keeps jumping
|
||||
// // back to the specified window.
|
||||
//// Rectangle screenRect =
|
||||
//// runDevice.getDefaultConfiguration().getBounds();
|
||||
// }
|
||||
if (windowLocation == null) {
|
||||
if (editorDevice == runDevice) {
|
||||
// If sketches are to be shown on the same display as the editor,
|
||||
// provide the editor location so the sketch's main() can place it.
|
||||
Point editorLocation = editor.getLocation();
|
||||
params.append(PApplet.ARGS_EDITOR_LOCATION + "=" +
|
||||
editorLocation.x + "," + editorLocation.y);
|
||||
} else {
|
||||
// The sketch's main() will set a location centered on the new
|
||||
// display. It has to happen in main() because the width/height
|
||||
// of the sketch are not known here.
|
||||
// Set a location centered on the other display
|
||||
// Rectangle screenRect =
|
||||
// runDevice.getDefaultConfiguration().getBounds();
|
||||
// int runX =
|
||||
// params.add(PApplet.ARGS_LOCATION + "=" + runX + "," + runY);
|
||||
}
|
||||
} else {
|
||||
params.append(PApplet.ARGS_LOCATION + "=" +
|
||||
windowLocation.x + "," + windowLocation.y);
|
||||
}
|
||||
params.append(PApplet.ARGS_EXTERNAL);
|
||||
}
|
||||
|
||||
params.append(PApplet.ARGS_DISPLAY + "=" + runDisplay);
|
||||
|
||||
|
||||
if (present) {
|
||||
params.append(PApplet.ARGS_PRESENT);
|
||||
// if (Preferences.getBoolean("run.present.exclusive")) {
|
||||
// params.add(PApplet.ARGS_EXCLUSIVE);
|
||||
// }
|
||||
params.append(PApplet.ARGS_STOP_COLOR + "=" +
|
||||
Preferences.get("run.present.stop.color"));
|
||||
params.append(PApplet.ARGS_WINDOW_COLOR + "=" +
|
||||
Preferences.get("run.present.bgcolor"));
|
||||
}
|
||||
|
||||
// There was a PDE X hack that put this after the class name, but it was
|
||||
// removed for 3.0a6 because it would break the args passed to sketches.
|
||||
params.append(PApplet.ARGS_SKETCH_FOLDER + "=" + build.getSketchPath());
|
||||
|
||||
if (Toolkit.zoom(100) >= 200) { // Use 100 to bypass possible rounding in zoom()
|
||||
params.append(PApplet.ARGS_DENSITY + "=2");
|
||||
}
|
||||
|
||||
params.append(build.getSketchClassName());
|
||||
}
|
||||
// Add command-line arguments to be given to the sketch itself
|
||||
if (args != null) {
|
||||
params.append(args);
|
||||
}
|
||||
// Pass back the whole list
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
protected void launchJava(final String[] args) {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
// PApplet.println("java starting");
|
||||
vmReturnedError = false;
|
||||
process = PApplet.exec(args);
|
||||
try {
|
||||
// PApplet.println("java waiting");
|
||||
int result = process.waitFor();
|
||||
// PApplet.println("java done waiting");
|
||||
if (result != 0) {
|
||||
String[] errorStrings = PApplet.loadStrings(process.getErrorStream());
|
||||
String[] inputStrings = PApplet.loadStrings(process.getInputStream());
|
||||
|
||||
// PApplet.println("launchJava stderr:");
|
||||
// PApplet.println(errorStrings);
|
||||
// PApplet.println("launchJava stdout:");
|
||||
PApplet.printArray(inputStrings);
|
||||
|
||||
if (errorStrings != null && errorStrings.length > 1) {
|
||||
if (errorStrings[0].indexOf("Invalid maximum heap size") != -1) {
|
||||
Messages.showWarning("Way Too High",
|
||||
"Please lower the value for \u201Cmaximum available memory\u201D in the\n" +
|
||||
"Preferences window. For more information, read Help \u2192 Troubleshooting.", null);
|
||||
} else {
|
||||
for (String err : errorStrings) {
|
||||
sketchErr.println(err);
|
||||
}
|
||||
sketchErr.println("Using startup command: " + PApplet.join(args, " "));
|
||||
}
|
||||
} else {
|
||||
//exc.printStackTrace();
|
||||
sketchErr.println("Could not run the sketch (Target VM failed to initialize).");
|
||||
if (Preferences.getBoolean("run.options.memory")) {
|
||||
// Only mention this if they've even altered the memory setup
|
||||
sketchErr.println("Make sure that you haven't set the maximum available memory too high.");
|
||||
}
|
||||
sketchErr.println("For more information, read revisions.txt and Help \u2192 Troubleshooting.");
|
||||
}
|
||||
// changing this to separate editor and listener [091124]
|
||||
//if (editor != null) {
|
||||
listener.statusError("Could not run the sketch.");
|
||||
vmReturnedError = true;
|
||||
//}
|
||||
// return null;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate the trace.
|
||||
* Enable events, start thread to display events,
|
||||
* start threads to forward remote error and output streams,
|
||||
* resume the remote VM, wait for the final event, and shutdown.
|
||||
*/
|
||||
protected void generateTrace() {
|
||||
//vm.setDebugTraceMode(debugTraceMode);
|
||||
// vm.setDebugTraceMode(VirtualMachine.TRACE_ALL);
|
||||
// vm.setDebugTraceMode(VirtualMachine.TRACE_NONE); // formerly, seems to have no effect
|
||||
try {
|
||||
// Calling this seems to set something internally to make the
|
||||
// Eclipse JDI wake up. Without it, an ObjectCollectedException
|
||||
// is thrown on excReq.enable(). No idea why this works,
|
||||
// but at least exception handling has returned. (Suspect that it may
|
||||
// block until all or at least some threads are available, meaning
|
||||
// that the app has launched and we have legit objects to talk to).
|
||||
vm.allThreads();
|
||||
// The bug may not have been noticed because the test suite waits for
|
||||
// a thread to be available, and queries it by calling allThreads().
|
||||
// See org.eclipse.debug.jdi.tests.AbstractJDITest for the example.
|
||||
|
||||
EventRequestManager mgr = vm.eventRequestManager();
|
||||
// get only the uncaught exceptions
|
||||
ExceptionRequest excReq = mgr.createExceptionRequest(null, false, true);
|
||||
// this version reports all exceptions, caught or uncaught
|
||||
// suspend so we can step
|
||||
excReq.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
||||
excReq.enable();
|
||||
} catch (VMDisconnectedException ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
Thread eventThread = new Thread() {
|
||||
public void run() {
|
||||
try {
|
||||
boolean connected = true;
|
||||
while (connected) {
|
||||
EventQueue eventQueue = vm.eventQueue();
|
||||
// remove() blocks until event(s) available
|
||||
EventSet eventSet = eventQueue.remove();
|
||||
// listener.vmEvent(eventSet);
|
||||
|
||||
for (Event event : eventSet) {
|
||||
// System.out.println("EventThread.handleEvent -> " + event);
|
||||
if (event instanceof VMStartEvent) {
|
||||
vm.resume();
|
||||
} else if (event instanceof ExceptionEvent) {
|
||||
// for (ThreadReference thread : vm.allThreads()) {
|
||||
// System.out.println("thread : " + thread);
|
||||
//// thread.suspend();
|
||||
// }
|
||||
exceptionEvent((ExceptionEvent) event);
|
||||
} else if (event instanceof VMDisconnectEvent) {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// } catch (VMDisconnectedException e) {
|
||||
// Logger.getLogger(VMEventReader.class.getName()).log(Level.INFO, "VMEventReader quit on VM disconnect");
|
||||
} catch (Exception e) {
|
||||
System.err.println("crashed in event thread due to " + e.getMessage());
|
||||
// Logger.getLogger(VMEventReader.class.getName()).log(Level.SEVERE, "VMEventReader quit", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
eventThread.start();
|
||||
|
||||
|
||||
errThread =
|
||||
new MessageSiphon(process.getErrorStream(), this).getThread();
|
||||
|
||||
outThread = new StreamRedirectThread("JVM stdout Reader",
|
||||
process.getInputStream(),
|
||||
sketchOut);
|
||||
errThread.start();
|
||||
outThread.start();
|
||||
|
||||
// Shutdown begins when event thread terminates
|
||||
try {
|
||||
if (eventThread != null) eventThread.join(); // is this the problem?
|
||||
|
||||
// System.out.println("in here");
|
||||
// Bug #852 tracked to this next line in the code.
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=852
|
||||
errThread.join(); // Make sure output is forwarded
|
||||
// System.out.println("and then");
|
||||
outThread.join(); // before we exit
|
||||
// System.out.println("finished join for errThread and outThread");
|
||||
|
||||
// At this point, disable the run button.
|
||||
// This happens when the sketch is exited by hitting ESC,
|
||||
// or the user manually closes the sketch window.
|
||||
// TODO this should be handled better, should it not?
|
||||
if (editor != null) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
editor.onRunnerExiting(Runner.this);
|
||||
});
|
||||
}
|
||||
} catch (InterruptedException exc) {
|
||||
// we don't interrupt
|
||||
}
|
||||
//System.out.println("and leaving");
|
||||
}
|
||||
|
||||
|
||||
protected Connector findConnector(String connectorName) {
|
||||
// List connectors =
|
||||
// com.sun.jdi.Bootstrap.virtualMachineManager().allConnectors();
|
||||
List<Connector> connectors =
|
||||
org.eclipse.jdi.Bootstrap.virtualMachineManager().allConnectors();
|
||||
|
||||
// // debug: code to list available connectors
|
||||
// Iterator iter2 = connectors.iterator();
|
||||
// while (iter2.hasNext()) {
|
||||
// Connector connector = (Connector)iter2.next();
|
||||
// System.out.println("connector name is " + connector.name());
|
||||
// }
|
||||
|
||||
for (Object c : connectors) {
|
||||
Connector connector = (Connector) c;
|
||||
// System.out.println(connector.name());
|
||||
// }
|
||||
// Iterator iter = connectors.iterator();
|
||||
// while (iter.hasNext()) {
|
||||
// Connector connector = (Connector)iter.next();
|
||||
if (connector.name().equals(connectorName)) {
|
||||
return connector;
|
||||
}
|
||||
}
|
||||
Messages.showError("Compiler Error",
|
||||
"findConnector() failed to find " +
|
||||
connectorName + " inside Runner", null);
|
||||
return null; // Not reachable
|
||||
}
|
||||
|
||||
|
||||
public void exceptionEvent(ExceptionEvent event) {
|
||||
ObjectReference or = event.exception();
|
||||
ReferenceType rt = or.referenceType();
|
||||
String exceptionName = rt.name();
|
||||
//Field messageField = Throwable.class.getField("detailMessage");
|
||||
Field messageField = rt.fieldByName("detailMessage");
|
||||
// System.out.println("field " + messageField);
|
||||
Value messageValue = or.getValue(messageField);
|
||||
// System.out.println("mess val " + messageValue);
|
||||
|
||||
//"java.lang.ArrayIndexOutOfBoundsException"
|
||||
int last = exceptionName.lastIndexOf('.');
|
||||
String message = exceptionName.substring(last + 1);
|
||||
if (messageValue != null) {
|
||||
String messageStr = messageValue.toString();
|
||||
if (messageStr.startsWith("\"")) {
|
||||
messageStr = messageStr.substring(1, messageStr.length() - 1);
|
||||
}
|
||||
message += ": " + messageStr;
|
||||
}
|
||||
// System.out.println("mess type " + messageValue.type());
|
||||
//StringReference messageReference = (StringReference) messageValue.type();
|
||||
|
||||
// First just report the exception and its placement
|
||||
reportException(message, or, event.thread());
|
||||
// Then try to pretty it up with a better message
|
||||
handleCommonErrors(exceptionName, message, listener, sketchErr);
|
||||
|
||||
if (editor != null) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
editor.onRunnerExiting(Runner.this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provide more useful explanations of common error messages, perhaps with
|
||||
* a short message in the status area, and (if necessary) a longer message
|
||||
* in the console.
|
||||
*
|
||||
* @param exceptionClass Class name causing the error (with full package name)
|
||||
* @param message The message from the exception
|
||||
* @param listener The Editor or command line interface that's listening for errors
|
||||
* @return true if the error was purtified, false otherwise
|
||||
*/
|
||||
public static boolean handleCommonErrors(final String exceptionClass,
|
||||
final String message,
|
||||
final RunnerListener listener,
|
||||
final PrintStream err) {
|
||||
if (exceptionClass.equals("java.lang.OutOfMemoryError")) {
|
||||
if (message.contains("exceeds VM budget")) {
|
||||
// TODO this is a kludge for Android, since there's no memory preference
|
||||
listener.statusError("OutOfMemoryError: This code attempts to use more memory than available.");
|
||||
err.println("An OutOfMemoryError means that your code is either using up too much memory");
|
||||
err.println("because of a bug (e.g. creating an array that's too large, or unintentionally");
|
||||
err.println("loading thousands of images), or simply that it's trying to use more memory");
|
||||
err.println("than what is supported by the current device.");
|
||||
} else {
|
||||
listener.statusError("OutOfMemoryError: You may need to increase the memory setting in Preferences.");
|
||||
err.println("An OutOfMemoryError means that your code is either using up too much memory");
|
||||
err.println("because of a bug (e.g. creating an array that's too large, or unintentionally");
|
||||
err.println("loading thousands of images), or that your sketch may need more memory to run.");
|
||||
err.println("If your sketch uses a lot of memory (for instance if it loads a lot of data files)");
|
||||
err.println("you can increase the memory available to your sketch using the Preferences window.");
|
||||
}
|
||||
} else if (exceptionClass.equals("java.lang.UnsatisfiedLinkError")) {
|
||||
listener.statusError("A library used by this sketch is not installed properly.");
|
||||
if (PApplet.platform == PConstants.LINUX) {
|
||||
err.println(message);
|
||||
}
|
||||
err.println("A library relies on native code that's not available.");
|
||||
err.println("Or only works properly when the sketch is run as a " +
|
||||
((Platform.getNativeBits() == 32) ? "64-bit" : "32-bit") + " application.");
|
||||
|
||||
} else if (exceptionClass.equals("java.lang.StackOverflowError")) {
|
||||
listener.statusError("StackOverflowError: This sketch is attempting too much recursion.");
|
||||
err.println("A StackOverflowError means that you have a bug that's causing a function");
|
||||
err.println("to be called recursively (it's calling itself and going in circles),");
|
||||
err.println("or you're intentionally calling a recursive function too much,");
|
||||
err.println("and your code should be rewritten in a more efficient manner.");
|
||||
|
||||
} else if (exceptionClass.equals("java.lang.UnsupportedClassVersionError")) {
|
||||
listener.statusError("UnsupportedClassVersionError: A library is using code compiled with an unsupported version of Java.");
|
||||
err.println("This version of Processing only supports libraries and JAR files compiled for Java 1.8 or earlier.");
|
||||
err.println("A library used by this sketch was compiled for Java 1.9 or later, ");
|
||||
err.println("and needs to be recompiled to be compatible with Java 1.8.");
|
||||
|
||||
} else if (exceptionClass.equals("java.lang.NoSuchMethodError") ||
|
||||
exceptionClass.equals("java.lang.NoSuchFieldError")) {
|
||||
listener.statusError(exceptionClass.substring(10) + ": " +
|
||||
"You may be using a library that's incompatible " +
|
||||
"with this version of Processing.");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// TODO: This may be called more than one time per error in the VM,
|
||||
// presumably because exceptions might be wrapped inside others,
|
||||
// and this will fire for both.
|
||||
protected void reportException(String message, ObjectReference or, ThreadReference thread) {
|
||||
listener.statusError(findException(message, or, thread));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move through a list of stack frames, searching for references to code
|
||||
* found in the current sketch. Return with a RunnerException that contains
|
||||
* the location of the error, or if nothing is found, just return with a
|
||||
* RunnerException that wraps the error message itself.
|
||||
*/
|
||||
protected SketchException findException(String message, ObjectReference or, ThreadReference thread) {
|
||||
try {
|
||||
// use to dump the stack for debugging
|
||||
// for (StackFrame frame : thread.frames()) {
|
||||
// System.out.println("frame: " + frame);
|
||||
// }
|
||||
|
||||
List<StackFrame> frames = thread.frames();
|
||||
for (StackFrame frame : frames) {
|
||||
try {
|
||||
Location location = frame.location();
|
||||
String filename = null;
|
||||
filename = location.sourceName();
|
||||
int lineNumber = location.lineNumber() - 1;
|
||||
SketchException rex =
|
||||
build.placeException(message, filename, lineNumber);
|
||||
if (rex != null) {
|
||||
return rex;
|
||||
}
|
||||
} catch (AbsentInformationException e) {
|
||||
// Any of the thread.blah() methods can throw an AbsentInformationEx
|
||||
// if that bit of data is missing. If so, just write out the error
|
||||
// message to the console.
|
||||
//e.printStackTrace(); // not useful
|
||||
exception = new SketchException(message);
|
||||
exception.hideStackTrace();
|
||||
listener.statusError(exception);
|
||||
}
|
||||
}
|
||||
} catch (IncompatibleThreadStateException e) {
|
||||
// This shouldn't happen, but if it does, print the exception in case
|
||||
// it's something that needs to be debugged separately.
|
||||
e.printStackTrace(sketchErr);
|
||||
} catch (Exception e) {
|
||||
// stack overflows seem to trip in frame.location() above
|
||||
// ignore this case so that the actual error gets reported to the user
|
||||
if ("StackOverflowError".equals(message) == false) {
|
||||
e.printStackTrace(sketchErr);
|
||||
}
|
||||
}
|
||||
// before giving up, try to extract from the throwable object itself
|
||||
// since sometimes exceptions are re-thrown from a different context
|
||||
try {
|
||||
// assume object reference is Throwable, get stack trace
|
||||
Method method = ((ClassType) or.referenceType()).concreteMethodByName("getStackTrace", "()[Ljava/lang/StackTraceElement;");
|
||||
ArrayReference result = (ArrayReference) or.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
||||
// iterate through stack frames and pull filename and line number for each
|
||||
for (Value val: result.getValues()) {
|
||||
ObjectReference ref = (ObjectReference)val;
|
||||
method = ((ClassType) ref.referenceType()).concreteMethodByName("getFileName", "()Ljava/lang/String;");
|
||||
StringReference strref = (StringReference) ref.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
||||
String filename = strref == null ? "Unknown Source" : strref.value();
|
||||
method = ((ClassType) ref.referenceType()).concreteMethodByName("getLineNumber", "()I");
|
||||
IntegerValue intval = (IntegerValue) ref.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
||||
int lineNumber = intval.intValue() - 1;
|
||||
SketchException rex =
|
||||
build.placeException(message, filename, lineNumber);
|
||||
if (rex != null) {
|
||||
return rex;
|
||||
}
|
||||
}
|
||||
// for (Method m : ((ClassType) or.referenceType()).allMethods()) {
|
||||
// System.out.println(m + " | " + m.signature() + " | " + m.genericSignature());
|
||||
// }
|
||||
// Implemented for 2.0b9, writes a stack trace when there's an internal error inside core.
|
||||
method = ((ClassType) or.referenceType()).concreteMethodByName("printStackTrace", "()V");
|
||||
// System.err.println("got method " + method);
|
||||
or.invokeMethod(thread, method, new ArrayList<Value>(), ObjectReference.INVOKE_SINGLE_THREADED);
|
||||
|
||||
} catch (Exception e) {
|
||||
// stack overflows will make the exception handling above trip again
|
||||
// ignore this case so that the actual error gets reported to the user
|
||||
if ("StackOverflowError".equals(message) == false) {
|
||||
e.printStackTrace(sketchErr);
|
||||
}
|
||||
}
|
||||
// Give up, nothing found inside the pile of stack frames
|
||||
SketchException rex = new SketchException(message);
|
||||
// exception is being created /here/, so stack trace is not useful
|
||||
rex.hideStackTrace();
|
||||
return rex;
|
||||
}
|
||||
|
||||
|
||||
public void close() {
|
||||
synchronized (cancelLock) {
|
||||
cancelled = true;
|
||||
|
||||
// TODO make sure stop() has already been called to exit the sketch
|
||||
|
||||
// TODO actually kill off the vm here
|
||||
if (vm != null) {
|
||||
try {
|
||||
vm.exit(0);
|
||||
|
||||
} catch (com.sun.jdi.VMDisconnectedException vmde) {
|
||||
// if the vm has disconnected on its own, ignore message
|
||||
//System.out.println("harmless disconnect " + vmde.getMessage());
|
||||
// TODO shouldn't need to do this, need to do more cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// made synchronized for 0087
|
||||
// attempted to remove synchronized for 0136 to fix bug #775 (no luck tho)
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=775
|
||||
synchronized public void message(String s) {
|
||||
// System.out.println("M" + s.length() + ":" + s.trim()); // + "MMM" + s.length());
|
||||
|
||||
// this eats the CRLFs on the lines.. oops.. do it later
|
||||
//if (s.trim().length() == 0) return;
|
||||
|
||||
// this is PApplet sending a message (via System.out.println)
|
||||
// that signals that the applet has been quit.
|
||||
if (s.indexOf(PApplet.EXTERNAL_STOP) == 0) {
|
||||
//System.out.println("external: quit");
|
||||
if (editor != null) {
|
||||
// editor.internalCloseRunner(); // [091124]
|
||||
// editor.handleStop(); // prior to 0192
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
editor.internalCloseRunner(); // 0192
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// this is the PApplet sending us a message that the applet
|
||||
// is being moved to a new window location
|
||||
if (s.indexOf(PApplet.EXTERNAL_MOVE) == 0) {
|
||||
String nums = s.substring(s.indexOf(' ') + 1).trim();
|
||||
int space = nums.indexOf(' ');
|
||||
int left = Integer.parseInt(nums.substring(0, space));
|
||||
int top = Integer.parseInt(nums.substring(space + 1));
|
||||
// this is only fired when connected to an editor
|
||||
editor.setSketchLocation(new Point(left, top));
|
||||
//System.out.println("external: move to " + left + " " + top);
|
||||
return;
|
||||
}
|
||||
|
||||
// these are used for debugging, in case there are concerns
|
||||
// that some errors aren't coming through properly
|
||||
// if (s.length() > 2) {
|
||||
// System.err.println(newMessage);
|
||||
// System.err.println("message " + s.length() + ":" + s);
|
||||
// }
|
||||
|
||||
// always shove out the message, since it might not fall under
|
||||
// the same setup as we're expecting
|
||||
sketchErr.print(s);
|
||||
//System.err.println("[" + s.length() + "] " + s);
|
||||
sketchErr.flush();
|
||||
}
|
||||
}
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.tweak;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import processing.mode.java.pdex.JavaTextAreaPainter;
|
||||
|
||||
|
||||
public class ColorControlBox {
|
||||
public boolean visible;
|
||||
ArrayList<Handle> handles;
|
||||
ColorMode colorMode;
|
||||
Color color;
|
||||
boolean ilegalColor = false;
|
||||
boolean isBW;
|
||||
boolean isHex;
|
||||
|
||||
String drawContext;
|
||||
|
||||
// interface
|
||||
int x, y, width, height;
|
||||
JavaTextAreaPainter painter;
|
||||
|
||||
|
||||
public ColorControlBox(String context, ColorMode mode,
|
||||
ArrayList<Handle> handles) {
|
||||
this.drawContext = context;
|
||||
this.colorMode = mode;
|
||||
this.handles = handles;
|
||||
|
||||
// add this box to the handles so they can update this color on change
|
||||
for (Handle h : handles) {
|
||||
h.setColorBox(this);
|
||||
}
|
||||
|
||||
isBW = isGrayScale();
|
||||
isHex = isHexColor();
|
||||
color = getCurrentColor();
|
||||
|
||||
visible = Settings.alwaysShowColorBoxes;
|
||||
}
|
||||
|
||||
|
||||
public void initInterface(JavaTextAreaPainter textAreaPainter,
|
||||
int x, int y, int w, int h) {
|
||||
this.painter = textAreaPainter;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = w;
|
||||
this.height = h;
|
||||
}
|
||||
|
||||
|
||||
public void setPos(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
|
||||
public void draw(Graphics2D g2d) {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
AffineTransform trans = g2d.getTransform();
|
||||
g2d.translate(x, y);
|
||||
|
||||
// draw current color
|
||||
g2d.setColor(color);
|
||||
g2d.fillRoundRect(0, 0, width, height, 5, 5);
|
||||
|
||||
// draw black outline
|
||||
g2d.setStroke(new BasicStroke(1));
|
||||
g2d.setColor(Color.BLACK);
|
||||
g2d.drawRoundRect(0, 0, width, height, 5, 5);
|
||||
|
||||
if (ilegalColor) {
|
||||
g2d.setColor(Color.RED);
|
||||
g2d.setStroke(new BasicStroke(2));
|
||||
g2d.drawLine(width-3, 3, 3, height-3);
|
||||
}
|
||||
|
||||
g2d.setTransform(trans);
|
||||
}
|
||||
|
||||
|
||||
public boolean isGrayScale() {
|
||||
if (handles.size() <= 2) {
|
||||
int value = handles.get(0).newValue.intValue();
|
||||
if ((value&0xff000000) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if number is hex or webcolor
|
||||
*/
|
||||
private boolean isHexColor() {
|
||||
if (handles.get(0).type == "hex" || handles.get(0).type == "webcolor") {
|
||||
int value = handles.get(0).value.intValue();
|
||||
if ((value & 0xff000000) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public Color getCurrentColor()
|
||||
{
|
||||
try {
|
||||
if (handles.size() == 1)
|
||||
{
|
||||
if (isBW) {
|
||||
// treat as color(gray)
|
||||
float gray = handles.get(0).newValue.floatValue();
|
||||
return verifiedGrayColor(gray);
|
||||
}
|
||||
else {
|
||||
// treat as color(argb)
|
||||
int argb = handles.get(0).newValue.intValue();
|
||||
return verifiedHexColor(argb);
|
||||
}
|
||||
}
|
||||
else if (handles.size() == 2)
|
||||
{
|
||||
if (isBW) {
|
||||
// color(gray, alpha)
|
||||
float gray = handles.get(0).newValue.floatValue();
|
||||
return verifiedGrayColor(gray);
|
||||
}
|
||||
else {
|
||||
// treat as color(argb, a)
|
||||
int argb = handles.get(0).newValue.intValue();
|
||||
float a = handles.get(1).newValue.floatValue();
|
||||
return verifiedHexColor(argb, a);
|
||||
}
|
||||
}
|
||||
else if (handles.size() == 3)
|
||||
{
|
||||
// color(v1, v2, v3)
|
||||
float v1 = handles.get(0).newValue.floatValue();
|
||||
float v2 = handles.get(1).newValue.floatValue();
|
||||
float v3 = handles.get(2).newValue.floatValue();
|
||||
|
||||
if (colorMode.modeType == ColorMode.RGB) {
|
||||
return verifiedRGBColor(v1, v2, v3, colorMode.aMax);
|
||||
}
|
||||
else {
|
||||
return verifiedHSBColor(v1, v2, v3, colorMode.aMax);
|
||||
}
|
||||
}
|
||||
else if (handles.size() == 4)
|
||||
{
|
||||
// color(v1, v2, v3, alpha)
|
||||
float v1 = handles.get(0).newValue.floatValue();
|
||||
float v2 = handles.get(1).newValue.floatValue();
|
||||
float v3 = handles.get(2).newValue.floatValue();
|
||||
float a = handles.get(3).newValue.floatValue();
|
||||
|
||||
if (colorMode.modeType == ColorMode.RGB) {
|
||||
return verifiedRGBColor(v1, v2, v3, a);
|
||||
}
|
||||
else {
|
||||
return verifiedHSBColor(v1, v2, v3, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.out.println("error parsing color value: " + e.toString());
|
||||
ilegalColor = true;
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
// couldn't figure out this color, return WHITE color
|
||||
ilegalColor = true;
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
private Color verifiedGrayColor(float gray)
|
||||
{
|
||||
if (gray < 0 || gray > colorMode.v1Max) {
|
||||
return colorError();
|
||||
}
|
||||
|
||||
ilegalColor = false;
|
||||
gray = gray/colorMode.v1Max * 255;
|
||||
return new Color((int)gray, (int)gray, (int)gray, 255);
|
||||
}
|
||||
|
||||
private Color verifiedHexColor(int argb)
|
||||
{
|
||||
int r = (argb>>16)&0xff;
|
||||
int g = (argb>>8)&0xff;
|
||||
int b = (argb&0xff);
|
||||
|
||||
ilegalColor = false;
|
||||
return new Color(r, g, b, 255);
|
||||
}
|
||||
|
||||
private Color verifiedHexColor(int argb, float alpha)
|
||||
{
|
||||
int r = (argb>>16)&0xff;
|
||||
int g = (argb>>8)&0xff;
|
||||
int b = (argb&0xff);
|
||||
|
||||
ilegalColor = false;
|
||||
return new Color(r, g, b, 255);
|
||||
}
|
||||
|
||||
public Color verifiedRGBColor(float r, float g, float b, float a)
|
||||
{
|
||||
if (r < 0 || r > colorMode.v1Max ||
|
||||
g < 0 || g > colorMode.v2Max ||
|
||||
b < 0 || b > colorMode.v3Max) {
|
||||
return colorError();
|
||||
}
|
||||
|
||||
ilegalColor = false;
|
||||
r = r/colorMode.v1Max * 255;
|
||||
g = g/colorMode.v2Max * 255;
|
||||
b = b/colorMode.v3Max * 255;
|
||||
return new Color((int)r, (int)g, (int)b, 255);
|
||||
}
|
||||
|
||||
public Color verifiedHSBColor(float h, float s, float b, float a)
|
||||
{
|
||||
if (h < 0 || h > colorMode.v1Max ||
|
||||
s < 0 || s > colorMode.v2Max ||
|
||||
b < 0 || b > colorMode.v3Max) {
|
||||
return colorError();
|
||||
}
|
||||
|
||||
ilegalColor = false;
|
||||
Color c = Color.getHSBColor(h/colorMode.v1Max, s/colorMode.v2Max, b/colorMode.v3Max);
|
||||
return new Color(c.getRed(), c.getGreen(), c.getBlue(), 255);
|
||||
}
|
||||
|
||||
private Color colorError()
|
||||
{
|
||||
ilegalColor = true;
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
public void colorChanged()
|
||||
{
|
||||
color = getCurrentColor();
|
||||
}
|
||||
|
||||
public int getTabIndex()
|
||||
{
|
||||
return handles.get(0).tabIndex;
|
||||
}
|
||||
|
||||
public int getLine()
|
||||
{
|
||||
return handles.get(0).line;
|
||||
}
|
||||
|
||||
public int getCharIndex()
|
||||
{
|
||||
int lastHandle = handles.size()-1;
|
||||
return handles.get(lastHandle).newEndChar + 2;
|
||||
}
|
||||
|
||||
/* Check if the point is in the box
|
||||
*
|
||||
*/
|
||||
public boolean pick(int mx, int my)
|
||||
{
|
||||
if (!visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mx>x && mx < x+width && my>y && my<y+height) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Only show the color box if mouse is on the same line
|
||||
*
|
||||
* return true if there was change
|
||||
*/
|
||||
public boolean setMouseY(int my)
|
||||
{
|
||||
boolean change = false;
|
||||
|
||||
if (my>y && my<y+height) {
|
||||
if (!visible) {
|
||||
change = true;
|
||||
}
|
||||
visible = true;
|
||||
}
|
||||
else {
|
||||
if (visible) {
|
||||
change = true;
|
||||
}
|
||||
visible = false;
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
/* Update the color numbers with the new values that were selected
|
||||
* in the color selector
|
||||
*
|
||||
* hue, saturation and brightness parameters are always 0-255
|
||||
*/
|
||||
public void selectorChanged(int hue, int saturation, int brightness)
|
||||
{
|
||||
if (isBW) {
|
||||
// color(gray) or color(gray, alpha)
|
||||
handles.get(0).setValue((float)hue/255*colorMode.v1Max);
|
||||
}
|
||||
else {
|
||||
if (handles.size() == 1 || handles.size() == 2) {
|
||||
// color(argb)
|
||||
int prevVal = handles.get(0).newValue.intValue();
|
||||
int prevAlpha = (prevVal>>24)&0xff;
|
||||
Color c = Color.getHSBColor((float)hue/255, (float)saturation/255, (float)brightness/255);
|
||||
int newVal = (prevAlpha<<24) | (c.getRed()<<16) | (c.getGreen()<<8) | (c.getBlue());
|
||||
handles.get(0).setValue(newVal);
|
||||
}
|
||||
else if (handles.size() == 3 || handles.size() == 4) {
|
||||
// color(v1, v2, v3) or color(v1, v2, v3, alpha)
|
||||
if (colorMode.modeType == ColorMode.HSB) {
|
||||
// HSB
|
||||
float v1 = (float)hue/255 * colorMode.v1Max;
|
||||
float v2 = (float)saturation/255 * colorMode.v2Max;
|
||||
float v3 = (float)brightness/255 * colorMode.v3Max;
|
||||
handles.get(0).setValue(v1);
|
||||
handles.get(1).setValue(v2);
|
||||
handles.get(2).setValue(v3);
|
||||
}
|
||||
else {
|
||||
// RGB
|
||||
Color c = Color.getHSBColor((float)hue/255, (float)saturation/255, (float)brightness/255);
|
||||
handles.get(0).setValue((float)c.getRed()/255*colorMode.v1Max);
|
||||
handles.get(1).setValue((float)c.getGreen()/255*colorMode.v2Max);
|
||||
handles.get(2).setValue((float)c.getBlue()/255*colorMode.v3Max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update our own color
|
||||
color = getCurrentColor();
|
||||
|
||||
// update code text painter so the user will see the changes
|
||||
painter.updateCodeText();
|
||||
painter.repaint();
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return handles.size() + " handles, color mode: " + colorMode.toString();
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.tweak;
|
||||
|
||||
|
||||
public class ColorMode {
|
||||
final static int RGB = 0;
|
||||
final static int HSB = 1;
|
||||
|
||||
float v1Max, v2Max, v3Max, aMax;
|
||||
int modeType;
|
||||
|
||||
boolean unrecognizedMode;
|
||||
String drawContext;
|
||||
|
||||
|
||||
public ColorMode(String context) {
|
||||
this.drawContext = context;
|
||||
modeType = RGB;
|
||||
v1Max = 255;
|
||||
v2Max = 255;
|
||||
v3Max = 255;
|
||||
aMax = 255;
|
||||
unrecognizedMode = false;
|
||||
}
|
||||
|
||||
|
||||
public ColorMode(String context, int type,
|
||||
float v1, float v2, float v3, float a) {
|
||||
this.drawContext = context;
|
||||
modeType = type;
|
||||
v1Max = v1;
|
||||
v2Max = v2;
|
||||
v3Max = v3;
|
||||
aMax = a;
|
||||
unrecognizedMode = false;
|
||||
}
|
||||
|
||||
|
||||
static public ColorMode fromString(String context, String mode) {
|
||||
try {
|
||||
String[] elements = mode.split(",");
|
||||
|
||||
// determine the type of the color mode
|
||||
int type = RGB;
|
||||
if (elements[0].trim().equals("HSB")) {
|
||||
type = HSB;
|
||||
}
|
||||
|
||||
if (elements.length == 1) {
|
||||
// colorMode in the form of colorMode(type)
|
||||
return new ColorMode(context, type, 255, 255, 255, 255);
|
||||
|
||||
} else if (elements.length == 2) {
|
||||
// colorMode in the form of colorMode(type, max)
|
||||
float max = Float.parseFloat(elements[1].trim());
|
||||
return new ColorMode(context, type, max, max, max, max);
|
||||
|
||||
} else if (elements.length == 4) {
|
||||
// colorMode in the form of colorMode(type, max1, max2, max3)
|
||||
float r = Float.parseFloat(elements[1].trim());
|
||||
float g = Float.parseFloat(elements[2].trim());
|
||||
float b = Float.parseFloat(elements[3].trim());
|
||||
return new ColorMode(context, type, r, g, b, 255);
|
||||
|
||||
} else if (elements.length == 5) {
|
||||
// colorMode in the form of colorMode(type, max1, max2, max3, maxA)
|
||||
float r = Float.parseFloat(elements[1].trim());
|
||||
float g = Float.parseFloat(elements[2].trim());
|
||||
float b = Float.parseFloat(elements[3].trim());
|
||||
float a = Float.parseFloat(elements[4].trim());
|
||||
return new ColorMode(context, type, r, g, b, a);
|
||||
}
|
||||
} catch (Exception e) { }
|
||||
|
||||
// if we failed to parse this mode (uses variables etc..)
|
||||
// we should still keep it so we'll know there is a mode declaration
|
||||
// and we should mark it as unrecognizable
|
||||
ColorMode newMode = new ColorMode(context);
|
||||
newMode.unrecognizedMode = true;
|
||||
return newMode;
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
String type = (modeType == RGB) ? "RGB" : "HSB";
|
||||
return "ColorMode: " + type +
|
||||
": (" + v1Max + ", " + v2Max + ", " + v3Max + ", " + aMax + ")";
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.tweak;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
|
||||
public class ColorScheme {
|
||||
private static ColorScheme instance = null;
|
||||
public Color redStrokeColor;
|
||||
public Color progressFillColor;
|
||||
public Color progressEmptyColor;
|
||||
public Color markerColor;
|
||||
public Color whitePaneColor;
|
||||
|
||||
|
||||
private ColorScheme() {
|
||||
redStrokeColor = new Color(160, 20, 20); // dark red
|
||||
progressEmptyColor = new Color(180, 180, 180, 200);
|
||||
progressFillColor = new Color(0, 0, 0, 200);
|
||||
markerColor = new Color(228, 200, 91, 127);
|
||||
whitePaneColor = new Color(255, 255, 255, 120);
|
||||
}
|
||||
|
||||
|
||||
public static ColorScheme getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ColorScheme();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.tweak;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
|
||||
|
||||
public class ColorSelector {
|
||||
int hue, saturation, brightness;
|
||||
|
||||
public JFrame frame;
|
||||
public ColorControlBox colorBox;
|
||||
ColorSelectorBox selectorBox;
|
||||
ColorSelectorSlider selectorSlider;
|
||||
SelectorTopBar topBar;
|
||||
|
||||
|
||||
public ColorSelector(ColorControlBox colorBox) {
|
||||
this.colorBox = colorBox;
|
||||
createFrame();
|
||||
}
|
||||
|
||||
|
||||
public void createFrame() {
|
||||
frame = new JFrame();
|
||||
frame.setBackground(Color.BLACK);
|
||||
|
||||
Box box = Box.createHorizontalBox();
|
||||
box.setBackground(Color.BLACK);
|
||||
|
||||
selectorSlider = new ColorSelectorSlider();
|
||||
|
||||
if (!colorBox.isBW) {
|
||||
selectorBox = new ColorSelectorBox();
|
||||
box.add(selectorBox);
|
||||
}
|
||||
|
||||
box.add(Box.createHorizontalGlue());
|
||||
box.add(selectorSlider, BorderLayout.CENTER);
|
||||
box.add(Box.createHorizontalGlue());
|
||||
|
||||
frame.getContentPane().add(box, BorderLayout.CENTER);
|
||||
frame.pack();
|
||||
frame.setResizable(false);
|
||||
frame.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||||
}
|
||||
|
||||
|
||||
public void show(int x, int y) {
|
||||
frame.setLocation(x, y);
|
||||
frame.setVisible(true);
|
||||
frame.repaint();
|
||||
}
|
||||
|
||||
|
||||
public void hide() {
|
||||
this.colorBox = null;
|
||||
frame.setVisible(false);
|
||||
}
|
||||
|
||||
|
||||
public void refreshColor() {
|
||||
if (!colorBox.ilegalColor) {
|
||||
setColor(colorBox.color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setColor(Color c) {
|
||||
if (selectorBox != null) {
|
||||
selectorBox.setToColor(c);
|
||||
}
|
||||
selectorSlider.setToColor(c);
|
||||
repaintSelector();
|
||||
}
|
||||
|
||||
|
||||
public void satBrightChanged() {
|
||||
repaintSelector();
|
||||
}
|
||||
|
||||
|
||||
public void hueChanged() {
|
||||
if (selectorBox != null) {
|
||||
selectorBox.renderBack();
|
||||
}
|
||||
repaintSelector();
|
||||
}
|
||||
|
||||
|
||||
public void repaintSelector() {
|
||||
if (selectorBox != null) {
|
||||
selectorBox.repaint();
|
||||
}
|
||||
selectorSlider.repaint();
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
class ColorSelectorBox extends JComponent {
|
||||
int lastX, lastY;
|
||||
BufferedImage backImg;
|
||||
|
||||
ColorSelectorBox() {
|
||||
if (!colorBox.ilegalColor) {
|
||||
setToColor(colorBox.color);
|
||||
}
|
||||
renderBack();
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
public void mousePressed(MouseEvent e) {
|
||||
updateMouse(e);
|
||||
}
|
||||
});
|
||||
addMouseMotionListener(new MouseAdapter() {
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
updateMouse(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void paintComponent(Graphics g) {
|
||||
g.drawImage(backImg, 0, 0, this);
|
||||
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
// otherwise the oval is hideous
|
||||
// TODO make a proper hidpi version of all this
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g.setColor(lastY < 128 ? Color.BLACK : Color.WHITE);
|
||||
AffineTransform tx = g2.getTransform();
|
||||
g2.translate(lastX, lastY);
|
||||
//g2.drawOval(0, 0, 5, 5);
|
||||
g2.drawOval(-3, -3, 6, 6);
|
||||
g2.drawLine(-8, 0, -6, 0);
|
||||
g2.drawLine(6, 0, 8, 0);
|
||||
g2.drawLine(0, -8, 0, -6);
|
||||
g2.drawLine(0, 6, 0, 8);
|
||||
g2.setTransform(tx);
|
||||
}
|
||||
|
||||
|
||||
public void renderBack() {
|
||||
int[] pixels = new int[256 * 256];
|
||||
int index = 0;
|
||||
for (int j = 0; j < 256; j++) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
pixels[index++] = // color(hue, i, 255-j);
|
||||
Color.HSBtoRGB(hue / 255f, (i / 255f), (255-j)/255f);
|
||||
}
|
||||
}
|
||||
backImg = new BufferedImage(256, 256, BufferedImage.TYPE_INT_RGB);
|
||||
backImg.getRaster().setDataElements(0, 0, 256, 256, pixels);
|
||||
}
|
||||
|
||||
|
||||
public void setToColor(Color c) {
|
||||
// set selector color
|
||||
float hsb[] = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
|
||||
saturation = (int) (hsb[1] * 255);
|
||||
brightness = (int) (hsb[2] * 255);
|
||||
lastX = saturation;
|
||||
lastY = 255 - brightness;
|
||||
}
|
||||
|
||||
|
||||
void updateMouse(MouseEvent event) {
|
||||
int mouseX = event.getX();
|
||||
int mouseY = event.getY();
|
||||
|
||||
if (mouseX >= 0 && mouseX < 256 &&
|
||||
mouseY >= 0 && mouseY < 256) {
|
||||
lastX = mouseX;
|
||||
lastY = mouseY;
|
||||
updateColor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void updateColor() {
|
||||
saturation = lastX;
|
||||
brightness = 255 - lastY;
|
||||
|
||||
satBrightChanged();
|
||||
colorBox.selectorChanged(hue, saturation, brightness);
|
||||
}
|
||||
|
||||
|
||||
public Dimension getPreferredSize() {
|
||||
return new Dimension(256, 256);
|
||||
}
|
||||
|
||||
|
||||
public Dimension getMinimumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
|
||||
public Dimension getMaximumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
class ColorSelectorSlider extends JComponent {
|
||||
final int WIDE = 30;
|
||||
BufferedImage backImg;
|
||||
int lastY;
|
||||
|
||||
ColorSelectorSlider() {
|
||||
// size(30, 255);
|
||||
// noLoop();
|
||||
// colorMode(HSB, 255, 255, 255);
|
||||
// strokeWeight(1);
|
||||
// noFill();
|
||||
// loadPixels();
|
||||
if (!colorBox.ilegalColor) {
|
||||
setToColor(colorBox.color);
|
||||
}
|
||||
|
||||
// draw the slider background
|
||||
renderBack();
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
public void mousePressed(MouseEvent e) {
|
||||
updateMouse(e);
|
||||
}
|
||||
});
|
||||
addMouseMotionListener(new MouseAdapter() {
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
updateMouse(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void paintComponent(Graphics g) {
|
||||
g.drawImage(backImg, 0, 0, this);
|
||||
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
// if (colorBox.isBW) {
|
||||
// stroke(lastY<128 ? 0 : 255);
|
||||
// }
|
||||
// else {
|
||||
// stroke(0);
|
||||
// }
|
||||
if (colorBox.isBW && lastY >= 128) {
|
||||
g2.setColor(Color.WHITE);
|
||||
} else {
|
||||
g2.setColor(Color.BLACK);
|
||||
}
|
||||
|
||||
AffineTransform tx = g2.getTransform();
|
||||
g2.translate(0, lastY);
|
||||
// draw left bracket
|
||||
// beginShape();
|
||||
// vertex(5, -2);
|
||||
// vertex(1, -2);
|
||||
// vertex(1, 2);
|
||||
// vertex(5, 2);
|
||||
// endShape();
|
||||
g.drawRect(1, -2, 6, 4);
|
||||
|
||||
// draw middle lines
|
||||
g.drawLine(13, 0, 17, 0);
|
||||
g.drawLine(15, -2, 15, 2);
|
||||
|
||||
// draw right bracket
|
||||
// beginShape();
|
||||
// vertex(24, -2);
|
||||
// vertex(28, -2);
|
||||
// vertex(28, 2);
|
||||
// vertex(24, 2);
|
||||
// endShape();
|
||||
g.drawRect(24, -2, 4, 4);
|
||||
g2.setTransform(tx);
|
||||
|
||||
/*
|
||||
if (colorBox.isBW) {
|
||||
// stroke(255);
|
||||
// rect(0, 0, 29, 254);
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawRect(0, 0, WIDE, 255);
|
||||
} else {
|
||||
// stroke(0);
|
||||
// line(0, 0, 0, 255);
|
||||
// line(29, 0, 29, 255);
|
||||
g.setColor(Color.BLACK);
|
||||
g.drawLine(0, 0, 0, 255);
|
||||
g.drawLine(29, 0, 29, 255);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
void renderBack() {
|
||||
int[] pixels = new int[WIDE * 256];
|
||||
int index = 0;
|
||||
|
||||
int argb = 0;
|
||||
for (int j = 0; j < 256; j++) {
|
||||
if (colorBox.isBW) {
|
||||
int gray = 255 - j;
|
||||
argb = 0xff000000 | (gray << 16) | (gray << 8) | gray;
|
||||
} else {
|
||||
// color(255-j, 255, 255);
|
||||
argb = Color.HSBtoRGB((255 - j) / 255f, 1, 1);
|
||||
}
|
||||
for (int i = 0; i < WIDE; i++) {
|
||||
pixels[index++] = argb;
|
||||
}
|
||||
}
|
||||
backImg = new BufferedImage(WIDE, 256, BufferedImage.TYPE_INT_RGB);
|
||||
backImg.getRaster().setDataElements(0, 0, WIDE, 256, pixels);
|
||||
}
|
||||
|
||||
|
||||
void setToColor(Color c) {
|
||||
// set slider position
|
||||
if (colorBox.isBW) {
|
||||
hue = c.getRed();
|
||||
} else {
|
||||
float hsb[] = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
|
||||
hue = (int)(hsb[0]*255);
|
||||
}
|
||||
lastY = 255 - hue;
|
||||
}
|
||||
|
||||
|
||||
void updateMouse(MouseEvent event) {
|
||||
int mouseY = event.getY();
|
||||
if (mouseY >= 0 && mouseY < 256) {
|
||||
lastY = mouseY;
|
||||
updateColor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void updateColor() {
|
||||
hue = 255 - lastY;
|
||||
hueChanged();
|
||||
colorBox.selectorChanged(hue, saturation, brightness);
|
||||
}
|
||||
|
||||
|
||||
public Dimension getPreferredSize() {
|
||||
return new Dimension(30, 255);
|
||||
}
|
||||
|
||||
|
||||
public Dimension getMinimumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
|
||||
public Dimension getMaximumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
public class SelectorTopBar extends JComponent {
|
||||
int barWidth;
|
||||
int barHeight = 16;
|
||||
|
||||
public SelectorTopBar(int w) {
|
||||
barWidth = w;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
g.setColor(Color.GRAY);
|
||||
Dimension size = getSize();
|
||||
g.fillRect(0, 0, size.width, size.height);
|
||||
}
|
||||
|
||||
public Dimension getPreferredSize() {
|
||||
return new Dimension(barWidth, barHeight);
|
||||
}
|
||||
|
||||
public Dimension getMinimumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
|
||||
public Dimension getMaximumSize() {
|
||||
return getPreferredSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.tweak;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Polygon;
|
||||
import java.awt.geom.AffineTransform;
|
||||
|
||||
|
||||
public class HProgressBar {
|
||||
int x, y, size, width;
|
||||
int pos;
|
||||
int lPolyX, rPolyX;
|
||||
Polygon rightPoly, leftPoly;
|
||||
|
||||
|
||||
public HProgressBar(int size, int width) {
|
||||
this.size = size;
|
||||
this.width = width;
|
||||
x = 0;
|
||||
y = 0;
|
||||
setPos(0);
|
||||
|
||||
int xl[] = {0, 0, -(int)(size/1.5)};
|
||||
int yl[] = {-(int)((float)size/3), (int)((float)size/3), 0};
|
||||
leftPoly = new Polygon(xl, yl, 3);
|
||||
int xr[] = {0, (int)(size/1.5), 0};
|
||||
int yr[] = {-(int)((float)size/3), 0, (int)((float)size/3)};
|
||||
rightPoly = new Polygon(xr, yr, 3);
|
||||
}
|
||||
|
||||
|
||||
public void setPos(int pos) {
|
||||
this.pos = pos;
|
||||
lPolyX = 0;
|
||||
rPolyX = 0;
|
||||
|
||||
if (pos > 0) {
|
||||
rPolyX = pos;
|
||||
}
|
||||
else if (pos < 0) {
|
||||
lPolyX = pos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setWidth(int width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
|
||||
public void draw(Graphics2D g2d) {
|
||||
AffineTransform trans = g2d.getTransform();
|
||||
g2d.translate(x, y);
|
||||
|
||||
// draw white cover on text line
|
||||
g2d.setColor(ColorScheme.getInstance().whitePaneColor);
|
||||
g2d.fillRect(-200+lPolyX, -size, 200-lPolyX-width/2, size+1);
|
||||
g2d.fillRect(width/2, -size, 200+rPolyX, size+1);
|
||||
|
||||
// draw left and right triangles and leading line
|
||||
g2d.setColor(ColorScheme.getInstance().progressFillColor);
|
||||
AffineTransform tmp = g2d.getTransform();
|
||||
g2d.translate(-width/2 - 5 + lPolyX, -size/2);
|
||||
g2d.fillRect(0, -1, -lPolyX, 2);
|
||||
g2d.fillPolygon(leftPoly);
|
||||
g2d.setTransform(tmp);
|
||||
g2d.translate(width/2 + 5 + rPolyX, -size/2);
|
||||
g2d.fillRect(-rPolyX, -1, rPolyX+1, 2);
|
||||
g2d.fillPolygon(rightPoly);
|
||||
g2d.setTransform(tmp);
|
||||
g2d.setTransform(trans);
|
||||
}
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.tweak;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
public class Handle {
|
||||
public String type;
|
||||
public String name;
|
||||
public String strValue;
|
||||
public String strNewValue;
|
||||
public int varIndex;
|
||||
public int startChar;
|
||||
public int endChar;
|
||||
public int newStartChar;
|
||||
public int newEndChar;
|
||||
public int line;
|
||||
int tabIndex;
|
||||
int decimalPlaces; // number of digits after the decimal point
|
||||
float incValue;
|
||||
|
||||
java.lang.Number value, newValue;
|
||||
String strDiff;
|
||||
|
||||
// connect with color control box
|
||||
ColorControlBox colorBox;
|
||||
|
||||
// interface
|
||||
int x, y, width, height;
|
||||
int xCenter, xCurrent, xLast;
|
||||
HProgressBar progBar = null;
|
||||
String textFormat;
|
||||
|
||||
// the client that sends the changes
|
||||
TweakClient tweakClient;
|
||||
|
||||
|
||||
public Handle(String t, String n, int vi, String v, int ti, int l, int sc,
|
||||
int ec, int dp) {
|
||||
type = t;
|
||||
name = n;
|
||||
varIndex = vi;
|
||||
strValue = v;
|
||||
tabIndex = ti;
|
||||
line = l;
|
||||
startChar = sc;
|
||||
endChar = ec;
|
||||
decimalPlaces = dp;
|
||||
|
||||
incValue = (float) (1 / Math.pow(10, decimalPlaces));
|
||||
|
||||
if ("int".equals(type)) {
|
||||
value = newValue = Integer.parseInt(strValue);
|
||||
strNewValue = strValue;
|
||||
textFormat = "%d";
|
||||
|
||||
} else if ("hex".equals(type)) {
|
||||
Long val = Long.parseLong(strValue.substring(2, strValue.length()), 16);
|
||||
value = newValue = val.intValue();
|
||||
strNewValue = strValue;
|
||||
textFormat = "0x%x";
|
||||
|
||||
} else if ("webcolor".equals(type)) {
|
||||
Long val = Long.parseLong(strValue.substring(1, strValue.length()), 16);
|
||||
val = val | 0xff000000;
|
||||
value = newValue = val.intValue();
|
||||
strNewValue = strValue;
|
||||
textFormat = "#%06x";
|
||||
|
||||
} else if ("float".equals(type)) {
|
||||
value = newValue = Float.parseFloat(strValue);
|
||||
strNewValue = strValue;
|
||||
textFormat = "%.0" + decimalPlaces + "f";
|
||||
}
|
||||
|
||||
newStartChar = startChar;
|
||||
newEndChar = endChar;
|
||||
}
|
||||
|
||||
|
||||
public void initInterface(int x, int y, int width, int height) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
// create drag ball
|
||||
progBar = new HProgressBar(height, width);
|
||||
}
|
||||
|
||||
|
||||
public void setCenterX(int mx) {
|
||||
xLast = xCurrent = xCenter = mx;
|
||||
}
|
||||
|
||||
|
||||
public void setCurrentX(int mx) {
|
||||
xLast = xCurrent;
|
||||
xCurrent = mx;
|
||||
|
||||
progBar.setPos(xCurrent - xCenter);
|
||||
|
||||
updateValue();
|
||||
}
|
||||
|
||||
|
||||
public void resetProgress() {
|
||||
progBar.setPos(0);
|
||||
}
|
||||
|
||||
|
||||
public void updateValue() {
|
||||
float change = getChange();
|
||||
|
||||
if ("int".equals(type)) {
|
||||
if (newValue.intValue() + (int) change > Integer.MAX_VALUE ||
|
||||
newValue.intValue() + (int) change < Integer.MIN_VALUE) {
|
||||
change = 0;
|
||||
return;
|
||||
}
|
||||
setValue(newValue.intValue() + (int) change);
|
||||
} else if ("hex".equals(type)) {
|
||||
setValue(newValue.intValue() + (int) change);
|
||||
} else if ("webcolor".equals(type)) {
|
||||
setValue(newValue.intValue() + (int) change);
|
||||
} else if ("float".equals(type)) {
|
||||
setValue(newValue.floatValue() + change);
|
||||
}
|
||||
|
||||
updateColorBox();
|
||||
}
|
||||
|
||||
|
||||
public void setValue(Number value) {
|
||||
if ("int".equals(type)) {
|
||||
newValue = value.intValue();
|
||||
strNewValue = String.format(Locale.US, textFormat, newValue.intValue());
|
||||
|
||||
} else if ("hex".equals(type)) {
|
||||
newValue = value.intValue();
|
||||
strNewValue = String.format(Locale.US, textFormat, newValue.intValue());
|
||||
|
||||
} else if ("webcolor".equals(type)) {
|
||||
newValue = value.intValue();
|
||||
// keep only RGB
|
||||
int val = (newValue.intValue() & 0xffffff);
|
||||
strNewValue = String.format(Locale.US, textFormat, val);
|
||||
|
||||
} else if ("float".equals(type)) {
|
||||
BigDecimal bd = new BigDecimal(value.floatValue());
|
||||
bd = bd.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP);
|
||||
newValue = bd.floatValue();
|
||||
strNewValue = String.format(Locale.US, textFormat, newValue.floatValue());
|
||||
}
|
||||
|
||||
// send new data to the server in the sketch
|
||||
sendNewValue();
|
||||
}
|
||||
|
||||
|
||||
public void updateColorBox() {
|
||||
if (colorBox != null) {
|
||||
colorBox.colorChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private float getChange() {
|
||||
int pixels = xCurrent - xLast;
|
||||
return pixels * incValue;
|
||||
}
|
||||
|
||||
|
||||
public void setPos(int nx, int ny) {
|
||||
x = nx;
|
||||
y = ny;
|
||||
}
|
||||
|
||||
|
||||
public void setWidth(int w) {
|
||||
width = w;
|
||||
progBar.setWidth(w);
|
||||
}
|
||||
|
||||
|
||||
public void draw(Graphics2D g2d, boolean hasFocus) {
|
||||
AffineTransform prevTrans = g2d.getTransform();
|
||||
g2d.translate(x, y);
|
||||
|
||||
// draw underline on the number
|
||||
g2d.setColor(ColorScheme.getInstance().progressFillColor);
|
||||
g2d.drawLine(0, 0, width, 0);
|
||||
|
||||
if (hasFocus) {
|
||||
if (progBar != null) {
|
||||
g2d.translate(width / 2, 2);
|
||||
progBar.draw(g2d);
|
||||
}
|
||||
}
|
||||
|
||||
g2d.setTransform(prevTrans);
|
||||
}
|
||||
|
||||
|
||||
public boolean pick(int mx, int my) {
|
||||
return pickText(mx, my);
|
||||
}
|
||||
|
||||
|
||||
public boolean pickText(int mx, int my) {
|
||||
return (mx > x - 2 && mx < x + width + 2 && my > y - height && my < y);
|
||||
}
|
||||
|
||||
|
||||
public boolean valueChanged() {
|
||||
if ("int".equals(type)) {
|
||||
return (value.intValue() != newValue.intValue());
|
||||
} else if ("hex".equals(type)) {
|
||||
return (value.intValue() != newValue.intValue());
|
||||
} else if ("webcolor".equals(type)) {
|
||||
return (value.intValue() != newValue.intValue());
|
||||
} else {
|
||||
return (value.floatValue() != newValue.floatValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setColorBox(ColorControlBox box) {
|
||||
colorBox = box;
|
||||
}
|
||||
|
||||
|
||||
public void setTweakClient(TweakClient client) {
|
||||
tweakClient = client;
|
||||
}
|
||||
|
||||
|
||||
public void sendNewValue() {
|
||||
int index = varIndex;
|
||||
try {
|
||||
if ("int".equals(type)) {
|
||||
tweakClient.sendInt(index, newValue.intValue());
|
||||
} else if ("hex".equals(type)) {
|
||||
tweakClient.sendInt(index, newValue.intValue());
|
||||
} else if ("webcolor".equals(type)) {
|
||||
tweakClient.sendInt(index, newValue.intValue());
|
||||
} else if ("float".equals(type)) {
|
||||
tweakClient.sendFloat(index, newValue.floatValue());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("error sending new value!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return type + " " + name + " = " + strValue + " (tab: " + tabIndex
|
||||
+ ", line: " + line + ", start: " + startChar + ", end: "
|
||||
+ endChar + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Used for sorting the handles by order of occurrence inside each tab
|
||||
*/
|
||||
class HandleComparator implements Comparator<Handle> {
|
||||
public int compare(Handle handle1, Handle handle2) {
|
||||
int tab = handle1.tabIndex - handle2.tabIndex;
|
||||
if (tab != 0) {
|
||||
return tab;
|
||||
}
|
||||
return handle1.startChar - handle2.startChar;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.tweak;
|
||||
|
||||
|
||||
public class Settings {
|
||||
public static boolean alwaysShowColorBoxes = true;
|
||||
}
|
||||
+1011
File diff suppressed because it is too large
Load Diff
+188
@@ -0,0 +1,188 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
Copyright (c) 2012-15 The Processing Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package processing.mode.java.tweak;
|
||||
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
|
||||
public class TweakClient {
|
||||
private DatagramSocket socket;
|
||||
private InetAddress address;
|
||||
private boolean initialized;
|
||||
private int sketchPort;
|
||||
|
||||
static final int VAR_INT = 0;
|
||||
static final int VAR_FLOAT = 1;
|
||||
static final int SHUTDOWN = 0xffffffff;
|
||||
|
||||
|
||||
public TweakClient(int sketchPort) {
|
||||
this.sketchPort = sketchPort;
|
||||
|
||||
try {
|
||||
socket = new DatagramSocket();
|
||||
// only local sketch is allowed
|
||||
address = InetAddress.getByName("127.0.0.1");
|
||||
initialized = true;
|
||||
|
||||
} catch (SocketException e) {
|
||||
initialized = false;
|
||||
|
||||
} catch (UnknownHostException e) {
|
||||
socket.close();
|
||||
initialized = false;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
socket.close();
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void shutdown() {
|
||||
if (initialized) {
|
||||
// send shutdown to the sketch
|
||||
sendShutdown();
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean sendInt(int index, int val) {
|
||||
if (initialized) {
|
||||
try {
|
||||
byte[] buf = new byte[12];
|
||||
ByteBuffer bb = ByteBuffer.wrap(buf);
|
||||
bb.putInt(0, VAR_INT);
|
||||
bb.putInt(4, index);
|
||||
bb.putInt(8, val);
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, sketchPort);
|
||||
socket.send(packet);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) { }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean sendFloat(int index, float val) {
|
||||
if (initialized) {
|
||||
try {
|
||||
byte[] buf = new byte[12];
|
||||
ByteBuffer bb = ByteBuffer.wrap(buf);
|
||||
bb.putInt(0, VAR_FLOAT);
|
||||
bb.putInt(4, index);
|
||||
bb.putFloat(8, val);
|
||||
socket.send(new DatagramPacket(buf, buf.length, address, sketchPort));
|
||||
return true;
|
||||
|
||||
} catch (Exception e) { }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean sendShutdown() {
|
||||
if (initialized) {
|
||||
try {
|
||||
byte[] buf = new byte[12];
|
||||
ByteBuffer bb = ByteBuffer.wrap(buf);
|
||||
bb.putInt(0, SHUTDOWN);
|
||||
socket.send(new DatagramPacket(buf, buf.length, address, sketchPort));
|
||||
return true;
|
||||
|
||||
} catch (Exception e) { }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static public String getServerCode(int listenPort,
|
||||
boolean hasInts, boolean hasFloats) {
|
||||
String serverCode = ""+
|
||||
"class TweakModeServer extends Thread\n"+
|
||||
"{\n"+
|
||||
" protected DatagramSocket socket = null;\n"+
|
||||
" protected boolean running = true;\n"+
|
||||
" final int INT_VAR = 0;\n"+
|
||||
" final int FLOAT_VAR = 1;\n"+
|
||||
" final int SHUTDOWN = 0xffffffff;\n"+
|
||||
" public TweakModeServer() {\n"+
|
||||
" this(\"TweakModeServer\");\n"+
|
||||
" }\n"+
|
||||
" public TweakModeServer(String name) {\n"+
|
||||
" super(name);\n"+
|
||||
" }\n"+
|
||||
" public void setup()\n"+
|
||||
" {\n"+
|
||||
" try {\n"+
|
||||
" socket = new DatagramSocket("+listenPort+");\n"+
|
||||
" socket.setSoTimeout(250);\n"+
|
||||
" } catch (IOException e) {\n"+
|
||||
" println(\"error: could not create TweakMode server socket\");\n"+
|
||||
" }\n"+
|
||||
" }\n"+
|
||||
" public void run()\n"+
|
||||
" {\n"+
|
||||
" byte[] buf = new byte[256];\n"+
|
||||
" while(running)\n"+
|
||||
" {\n"+
|
||||
" try {\n"+
|
||||
" DatagramPacket packet = new DatagramPacket(buf, buf.length);\n"+
|
||||
" socket.receive(packet);\n"+
|
||||
" ByteBuffer bb = ByteBuffer.wrap(buf);\n"+
|
||||
" int type = bb.getInt(0);\n"+
|
||||
" int index = bb.getInt(4);\n";
|
||||
|
||||
if (hasInts) {
|
||||
serverCode +=
|
||||
" if (type == INT_VAR) {\n"+
|
||||
" int val = bb.getInt(8);\n"+
|
||||
" tweakmode_int[index] = val;\n"+
|
||||
" }\n"+
|
||||
" else ";
|
||||
}
|
||||
if (hasFloats) {
|
||||
serverCode +=
|
||||
" if (type == FLOAT_VAR) {\n"+
|
||||
" float val = bb.getFloat(8);\n"+
|
||||
" tweakmode_float[index] = val;\n"+
|
||||
" }\n"+
|
||||
" else";
|
||||
}
|
||||
serverCode +=
|
||||
" if (type == SHUTDOWN) {\n"+
|
||||
" running = false;\n"+
|
||||
" }\n"+
|
||||
" } catch (SocketTimeoutException e) {\n"+
|
||||
" // nothing to do here just try receiving again\n"+
|
||||
" } catch (Exception e) {\n"+
|
||||
" }\n"+
|
||||
" }\n"+
|
||||
" socket.close();\n"+
|
||||
" }\n"+
|
||||
"}\n\n\n";
|
||||
|
||||
return serverCode;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user