Massive refactoring of the editor plugin. Added a feature plugin and update site controlling three smaller plugins. Tons of code cleanup. Builds and runs, but still missing a proper compile chain.

This commit is contained in:
lonnen
2010-08-18 16:30:40 +00:00
parent fc013f294d
commit 7cd562553b
2018 changed files with 19629 additions and 3595 deletions

View File

@@ -0,0 +1,78 @@
package processing.plugin.ui;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
/**
* Container class for all the methods related to logging exceptions and other such
* information that is useful to have written to a log file somewhere.
*
* @author lonnen
*/
public class ProcessingLog {
// Who needs a constructor?
/**
* Convenience method for appending a string to the log file.
* Don't use this if there is an error.
*
* @param message something to append to the log file
*/
public static void logInfo(String message){
log(IStatus.INFO, IStatus.OK, message, null);
}
/**
* Convenience method for appending an unexpected exception to the log file
*
* @param exception some problem
*/
public static void logError(Throwable exception){
logError("Unexpected Exception", exception);
}
/**
* Convenience method for appending an exception with a message
*
* @param message a message, preferably something about the problem
* @param exception the problem
*/
public static void logError(String message, Throwable exception){
log(IStatus.ERROR, IStatus.OK, message, exception);
}
/**
* Adapter method that creates the appropriate status to be logged
*
* @param severity integer code indicating the type of message
* @param code plug-in-specific status code
* @param message a human readable message
*/
public static void log(int severity, int code, String message, Throwable exception){
log(createStatus(severity, code, message, exception));
}
/**
* Creates a status object to log
*
* @param severity integer code indicating the type of message
* @param code plug-in-specific status code
* @param message a human readable message
* @param a low-level exception, or null
* @return status object
*/
public static IStatus createStatus(int severity, int code, String message, Throwable exception){
return new Status(severity, ProcessingPlugin.PLUGIN_ID, code, message, exception);
}
/**
* Write a status to the log
*
* @param status
*/
public static void log(IStatus status){
ProcessingPlugin.getDefault().getLog().log(status);
}
}

View File

@@ -0,0 +1,110 @@
package processing.plugin.ui;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
import processing.plugin.ui.processingeditor.ProcessingPartitionScanner;
import processing.plugin.ui.processingeditor.language.ProcessingCodeScanner;
import processing.plugin.ui.processingeditor.util.ProcessingColorProvider;
/**
* Controls Processing Plugin user interface elements.
* <p>
* It provides convenience methods and controls elements shared by editors and other UI elements
* such as the document provider or the partitioning scanner.
*/
public class ProcessingPlugin extends AbstractUIPlugin {
/** The ID of the Processing UI Plugin */
public static final String PLUGIN_ID = "processing.plugin.ui"; //$NON-NLS-1$
/** The ID of a planned but unimplemented Processing Perspective */ //TODO Implement P5 Perspective
public static final String ID_PERSPECTIVE = PLUGIN_ID + ".ProcessingPerspective";
/** The ID of the processing */
public static final String PROCESSING_PARTITIONING = "__processing_partitioning";
/** The shared plugin instance */
private static ProcessingPlugin plugin;
// Supporting Objects
private ProcessingPartitionScanner fPartitionScanner;
private ProcessingColorProvider fColorProvider;
private ProcessingCodeScanner fCodeScanner;
/** Initialized the shared instance. */
public ProcessingPlugin() {
super();
plugin = this;
}
/* Method declared in plug-in */
public void start(BundleContext context) throws Exception {
super.start(context);
// Other init stuff
// Don't forget to shut it down in stop()!
}
/* Method declared in plug-in */
public void stop(BundleContext context) throws Exception {
try{
plugin = null;
if(fPartitionScanner != null)
fPartitionScanner = null;
if(fColorProvider != null){
fColorProvider.dispose();
fColorProvider = null;
}
if(fCodeScanner != null)
fCodeScanner = null;
} finally {
super.stop(context);
}
}
/**
* Returns the shared instance
*
* @return the shared instance
*/
public static ProcessingPlugin getDefault() {
return plugin;
}
/**
* Return a scanner for creating Processing partitions.
* Processing uses Java's commenting scheme, so our partitioner is almost identical. Unlike
* the Java partitioner, however, the Processing scanner treats the JavaDoc comments as
* simple multiline comments.
*
* @return a scanner for creating Processing partitions
*/
public ProcessingPartitionScanner getProcessingPartitionScanner() {
if (fPartitionScanner == null)
fPartitionScanner= new ProcessingPartitionScanner();
return fPartitionScanner;
}
/**
* Returns the shared code scanner.
*
* @return the singleton Processing code scanner
*/
public RuleBasedScanner getProcessingCodeScanner() {
if (fCodeScanner == null)
fCodeScanner= new ProcessingCodeScanner(getProcessingColorProvider());
return fCodeScanner;
}
/**
* Returns the shared color provider.
*
* @return the singleton Processing color provider
*/
public ProcessingColorProvider getProcessingColorProvider() {
if (fColorProvider == null)
fColorProvider= new ProcessingColorProvider();
return fColorProvider;
}
}

View File

@@ -0,0 +1,30 @@
package processing.plugin.ui.processingeditor;
//import org.eclipse.jface.text.BadLocationException;
//import org.eclipse.jface.text.IDocument;
//import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.source.IAnnotationHover;
import org.eclipse.jface.text.source.ISourceViewer;
/**
* Provides the content in the presentation area of a mouse hover popup window.
*
* @author lonnen
* @see org.eclipse.jface.text.source.IAnnotationHover
*/
public class ProcessingAnnotationHover implements IAnnotationHover {
/* */
public String getHoverInfo(ISourceViewer sourceViewer, int lineNumber) {
// TODO write useful annotation hover, will require AST
// IDocument document= sourceViewer.getDocument();
//
// try {
// IRegion info= document.getLineInformation(lineNumber);
// return document.get(info.getOffset(), info.getLength());
// } catch (BadLocationException x) { }
return null;
}
}

View File

@@ -0,0 +1,27 @@
package processing.plugin.ui.processingeditor;
import org.eclipse.core.filebuffers.IDocumentSetupParticipant;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.rules.FastPartitioner;
import processing.plugin.ui.ProcessingPlugin;
import processing.plugin.ui.processingeditor.ProcessingPartitionScanner;
public class ProcessingDocumentSetupParticipant implements IDocumentSetupParticipant {
/** Empty Constructor */
public ProcessingDocumentSetupParticipant(){}
/** Set up the document*/
public void setup(IDocument document) {
if (document instanceof IDocumentExtension3) {
IDocumentExtension3 extension3= (IDocumentExtension3) document;
IDocumentPartitioner partitioner= new FastPartitioner(ProcessingPlugin.getDefault().getProcessingPartitionScanner(), ProcessingPartitionScanner.PARTITION_TYPES);
extension3.setDocumentPartitioner(ProcessingPlugin.PROCESSING_PARTITIONING, partitioner);
partitioner.connect(document);
}
}
}

View File

@@ -0,0 +1,88 @@
package processing.plugin.ui.processingeditor;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.editors.text.TextEditor;
public class ProcessingEditor extends TextEditor {
/** Projection Support handles annotations, etc.*/
private ProjectionSupport fProjectionSupport;
//TODO content assistance
//TODO outline
//TODO code folding
/** Default constructor */
public ProcessingEditor() { super(); }
/**
* Shut it down.
* <p>
* This should include any objects the editor privately maintains.
* In the future this may involve content assist, outline, etc.
*/
public void dispose() {
super.dispose();
}
public Object getAdapter(Class required){
if (fProjectionSupport != null) {
Object adapter= fProjectionSupport.getAdapter(getSourceViewer(), required);
if (adapter != null)
return adapter;
}
return super.getAdapter(required);
}
/**
* Initializes this editor and provides a <code>SourceViewerConfiguration</code>
*
* @see org.eclipse.jface.text.source.SourceViewerConfiguration
*/
protected void initializeEditor() {
super.initializeEditor();
setSourceViewerConfiguration(new ProcessingSourceViewerConfiguration());
}
protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
fAnnotationAccess= createAnnotationAccess();
fOverviewRuler= createOverviewRuler(getSharedColors());
ISourceViewer viewer= new ProjectionViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles);
// ensure decoration support has been created and configured.
// preferred over SourceViewerDecorationSupport due to impending API changes [lonnen] june 11, 2010
fSourceViewerDecorationSupport = getSourceViewerDecorationSupport(viewer);
return viewer;
}
/* @see org.eclipse.ui.texteditor.ExtendedTextEditor#createPartControl(org.eclipse.swt.widgets.Composite) */
public void createPartControl(Composite parent) {
super.createPartControl(parent);
ProjectionViewer viewer= (ProjectionViewer) getSourceViewer();
fProjectionSupport= new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors());
fProjectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error"); //$NON-NLS-1$
fProjectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning"); //$NON-NLS-1$
fProjectionSupport.install();
viewer.doOperation(ProjectionViewer.TOGGLE);
}
/* @see org.eclipse.ui.texteditor.AbstractTextEditor#adjustHighlightRange(int, int) */
protected void adjustHighlightRange(int offset, int length) {
ISourceViewer viewer= getSourceViewer();
if (viewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5) viewer;
extension.exposeModelRange(new Region(offset, length));
}
}
}

View File

@@ -0,0 +1,83 @@
package processing.plugin.ui.processingeditor;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.rules.EndOfLineRule;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IPredicateRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.MultiLineRule;
import org.eclipse.jface.text.rules.RuleBasedPartitionScanner;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WordRule;
public class ProcessingPartitionScanner extends RuleBasedPartitionScanner {
public static final String MULTILINE_COMMENT = "__multiline_comment";
public static final String[] PARTITION_TYPES = new String[] { MULTILINE_COMMENT };
/** Detector for empty comments. */
static class EmptyCommentDetector implements IWordDetector {
/* Method declared on IWordDetector */
public boolean isWordStart(char c) {
return (c == '/');
}
/* Method declared on IWordDetector */
public boolean isWordPart(char c) {
return (c == '*' || c == '/');
}
}
static class WordPredicateRule extends WordRule implements IPredicateRule {
private IToken fSuccessToken;
public WordPredicateRule(IToken successToken) {
super(new EmptyCommentDetector());
fSuccessToken= successToken;
addWord("/**/", fSuccessToken); //$NON-NLS-1$
}
/* @see org.eclipse.jface.text.rules.IPredicateRule#evaluate(ICharacterScanner, boolean) */
public IToken evaluate(ICharacterScanner scanner, boolean resume) {
return super.evaluate(scanner);
}
/* @see org.eclipse.jface.text.rules.IPredicateRule#getSuccessToken()*/
public IToken getSuccessToken() {
return fSuccessToken;
}
}
/** Create the partitioner and sets up the appropriate rules. */
public ProcessingPartitionScanner() {
super();
IToken comment= new Token(MULTILINE_COMMENT);
List rules= new ArrayList();
// Add rule for single line comments.
rules.add(new EndOfLineRule("//", Token.UNDEFINED)); //$NON-NLS-1$
// Add rule for strings and character constants.
rules.add(new SingleLineRule("\"", "\"", Token.UNDEFINED, '\\')); //$NON-NLS-2$ //$NON-NLS-1$
rules.add(new SingleLineRule("'", "'", Token.UNDEFINED, '\\')); //$NON-NLS-2$ //$NON-NLS-1$
// Add special case word rule.
rules.add(new WordPredicateRule(comment));
// Add rules for multi-line comments and javadoc.
rules.add(new MultiLineRule("/*", "*/", comment, (char) 0, true)); //$NON-NLS-1$ //$NON-NLS-2$
IPredicateRule[] result= new IPredicateRule[rules.size()];
rules.toArray(result);
setPredicateRules(result);
}
}

View File

@@ -0,0 +1,111 @@
package processing.plugin.ui.processingeditor;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextDoubleClickStrategy;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.rules.BufferedRuleBasedScanner;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.source.IAnnotationHover;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.swt.graphics.RGB;
import processing.plugin.ui.ProcessingPlugin;
import processing.plugin.ui.processingeditor.language.ProcessingAutoIndentStrategy;
import processing.plugin.ui.processingeditor.language.ProcessingCompletionProcessor;
import processing.plugin.ui.processingeditor.language.ProcessingDoubleClickSelector;
import processing.plugin.ui.processingeditor.util.ProcessingColorProvider;
public class ProcessingSourceViewerConfiguration extends SourceViewerConfiguration {
/** token scanner. */
static class SingleTokenScanner extends BufferedRuleBasedScanner {
public SingleTokenScanner(TextAttribute attribute) {
setDefaultReturnToken(new Token(attribute));
}
}
/** a boring default constructor */
public ProcessingSourceViewerConfiguration() { }
/* Method declared on SourceViewerConfiguration */
public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) { return new ProcessingAnnotationHover(); }
/* @see org.eclipse.jface.text.source.SourceViewerConfiguration#getAutoEditStrategies(org.eclipse.jface.text.source.ISourceViewer, java.lang.String) */
public IAutoEditStrategy[] getAutoEditStrategies(ISourceViewer sourceViewer, String contentType) {
IAutoEditStrategy strategy= (IDocument.DEFAULT_CONTENT_TYPE.equals(contentType) ? new ProcessingAutoIndentStrategy() : new DefaultIndentLineAutoEditStrategy());
return new IAutoEditStrategy[] { strategy };
}
/* @see org.eclipse.jface.text.source.SourceViewerConfiguration#getConfiguredDocumentPartitioning(org.eclipse.jface.text.source.ISourceViewer) */
public String getConfiguredDocumentPartitioning(ISourceViewer sourceViewer) {
return ProcessingPlugin.PROCESSING_PARTITIONING;
}
/* Method declared on SourceViewerConfiguration */
public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) {
return new String[] { IDocument.DEFAULT_CONTENT_TYPE, ProcessingPartitionScanner.MULTILINE_COMMENT };
}
/* Method declared on SourceViewerConfiguration */
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
ContentAssistant assistant= new ContentAssistant();
assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer));
assistant.setContentAssistProcessor(new ProcessingCompletionProcessor(), IDocument.DEFAULT_CONTENT_TYPE);
assistant.enableAutoActivation(true);
assistant.setAutoActivationDelay(500);
assistant.setProposalPopupOrientation(IContentAssistant.PROPOSAL_OVERLAY);
assistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
assistant.setContextInformationPopupBackground(ProcessingPlugin.getDefault().getProcessingColorProvider().getColor(new RGB(150, 150, 0)));
return assistant;
}
/* Method declared on SourceViewerConfiguration */
public String getDefaultPrefix(ISourceViewer sourceViewer, String contentType) {
return (IDocument.DEFAULT_CONTENT_TYPE.equals(contentType) ? "//" : null); //$NON-NLS-1$
}
/* Method declared on SourceViewerConfiguration */
public ITextDoubleClickStrategy getDoubleClickStrategy(ISourceViewer sourceViewer, String contentType) {
return new ProcessingDoubleClickSelector();
}
/* Method declared on SourceViewerConfiguration */
public String[] getIndentPrefixes(ISourceViewer sourceViewer, String contentType) {
return new String[] { "\t", " " }; //$NON-NLS-1$ //$NON-NLS-2$
}
/* Method declared on SourceViewerConfiguration */
public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
ProcessingColorProvider provider= ProcessingPlugin.getDefault().getProcessingColorProvider();
PresentationReconciler reconciler= new PresentationReconciler();
reconciler.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer));
DefaultDamagerRepairer dr= new DefaultDamagerRepairer(ProcessingPlugin.getDefault().getProcessingCodeScanner());
reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
dr= new DefaultDamagerRepairer(new SingleTokenScanner(new TextAttribute(provider.getColor(ProcessingColorProvider.COMMENT1))));
reconciler.setDamager(dr, ProcessingPartitionScanner.MULTILINE_COMMENT);
reconciler.setRepairer(dr, ProcessingPartitionScanner.MULTILINE_COMMENT);
return reconciler;
}
/* Method declared on SourceViewerConfiguration */
public int getTabWidth(ISourceViewer sourceViewer) { return 4; }
}

View File

@@ -0,0 +1,268 @@
package processing.plugin.ui.processingeditor.language;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextUtilities;
import processing.plugin.ui.ProcessingLog;
public class ProcessingAutoIndentStrategy extends
DefaultIndentLineAutoEditStrategy {
/** Boring constructor */
public ProcessingAutoIndentStrategy(){}
/* Method declared on IAutoIndentStrategy */
public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
if (c.length == 0 && c.text != null && endsWithDelimiter(d, c.text))
smartIndentAfterNewLine(d, c);
else if ("}".equals(c.text)) { //$NON-NLS-1$
smartInsertAfterBracket(d, c);
}
}
/**
* Returns whether or not the given text ends with one of the documents legal line delimiters.
*
* @param d the document
* @param txt the text
* @return <code>true</code> if <code>txt</code> ends with one of the document's line delimiters, <code>false</code> otherwise
*/
private boolean endsWithDelimiter(IDocument d, String txt) {
String[] delimiters= d.getLegalLineDelimiters();
if (delimiters != null)
return TextUtilities.endsWith(delimiters, txt) > -1;
return false;
}
/**
* Returns the line number of the next bracket after end.
*
* @param document - the document being parsed
* @param line - the line to start searching back from
* @param end - the end position to search back from
* @param closingBracketIncrease - the number of brackets to skip
* @return the line number of the next matching bracket after end
* @throws BadLocationException in case the line numbers are invalid in the document
*/
protected int findMatchingOpenBracket(IDocument document, int line, int end, int closingBracketIncrease) throws BadLocationException {
int start = document.getLineOffset(line);
int brackcount = getBracketCount(document, start, end, false) - closingBracketIncrease;
// sum up the brackets counts of each line (closing brackets count negative,
// opening positive) until we find a line the brings the count to zero
while (brackcount < 0) {
line--;
if (line < 0) {
return -1;
}
start= document.getLineOffset(line);
end= start + document.getLineLength(line) - 1;
brackcount += getBracketCount(document, start, end, false);
}
return line;
}
/**
* Returns the bracket value of a section of text. Closing brackets have a value of -1 and
* open brackets have a value of 1.
*
* @param document - the document being parsed
* @param start - the start position for the search
* @param end - the end position for the search
* @param ignoreCloseBrackets - whether or not to ignore closing brackets in the count
* @return the bracket value of a section of text
* @throws BadLocationException in case the positions are invalid in the document
*/
private int getBracketCount(IDocument document, int start, int end, boolean ignoreCloseBrackets) throws BadLocationException {
int begin = start;
int bracketcount = 0;
while (begin < end) {
char curr = document.getChar(begin);
begin++;
switch (curr) {
case '/' :
if (begin < end) {
char next = document.getChar(begin);
if (next == '*') {
// a comment starts, advance to the comment end
begin = getCommentEnd(document, begin + 1, end);
} else if (next == '/') {
// '//'-comment: nothing to do anymore on this line
begin= end;
}
}
break;
case '*' :
if (begin < end) {
char next = document.getChar(begin);
if (next == '/') {
// we have been in a comment: forget what we read before
bracketcount = 0;
begin++;
}
}
break;
case '{' :
bracketcount++;
ignoreCloseBrackets = false;
break;
case '}' :
if (!ignoreCloseBrackets) {
bracketcount--;
}
break;
case '"' :
case '\'' :
begin = getStringEnd(document, begin, end, curr);
break;
default :
}
}
return bracketcount;
}
/**
* Returns the end position of a comment starting at the given <code>position</code>.
*
* @param document - the document being parsed
* @param position - the start position for the search
* @param end - the end position for the search
* @return the end position of a comment starting at the given <code>position</code>
* @throws BadLocationException in case <code>position</code> and <code>end</code> are invalid in the document
*/
private int getCommentEnd(IDocument document, int position, int end) throws BadLocationException {
int currentPosition = position;
while (currentPosition < end) {
char curr = document.getChar(currentPosition);
currentPosition++;
if (curr == '*') {
if (currentPosition < end && document.getChar(currentPosition) == '/') {
return currentPosition + 1;
}
}
}
return end;
}
/**
* Returns the content of the given line without the leading whitespace.
*
* @param document - the document being parsed
* @param line - the line being searched
* @return the content of the given line without the leading whitespace
* @throws BadLocationException in case <code>line</code> is invalid in the document
*/
protected String getIndentOfLine(IDocument document, int line) throws BadLocationException {
if (line > -1) {
int start = document.getLineOffset(line);
int end= start + document.getLineLength(line) - 1;
int whiteend = findEndOfWhiteSpace(document, start, end);
return document.get(start, whiteend - start);
}
return "";
}
/**
* Returns the position of the <code>character</code> in the <code>document</code> after <code>position</code>.
*
* @param document - the document being parsed
* @param position - the position to start searching from
* @param end - the end of the document
* @param character - the character you are trying to match
* @return the next location of <code>character</code>
* @throws BadLocationException in case <code>position</code> is invalid in the document
*/
private int getStringEnd(IDocument document, int position, int end, char character) throws BadLocationException {
int currentPosition = position;
while (currentPosition < end) {
char currentCharacter= document.getChar(currentPosition);
currentPosition++;
if (currentCharacter == '\\') {
// ignore escaped characters
currentPosition++;
} else if (currentCharacter == character) {
return currentPosition;
}
}
return end;
}
/**
* Set the indent of a new line based on the command provided in the supplied document.
* @param document - the document being parsed
* @param command - the command being performed
*/
protected void smartIndentAfterNewLine(IDocument document, DocumentCommand command) {
int docLength = document.getLength();
if (command.offset == -1 || docLength == 0)
return;
try {
int p = (command.offset == docLength ? command.offset - 1 : command.offset);
int line = document.getLineOfOffset(p);
StringBuffer buf = new StringBuffer(command.text);
if (command.offset < docLength && document.getChar(command.offset) == '}') {
int indLine = findMatchingOpenBracket(document, line, command.offset, 0);
if (indLine == -1) {
indLine = line;
}
buf.append(getIndentOfLine(document, indLine));
} else {
int start = document.getLineOffset(line);
int whiteend= findEndOfWhiteSpace(document, start, command.offset);
buf.append(document.get(start, whiteend - start));
if (getBracketCount(document, start, command.offset, true) > 0) {
buf.append('\t');
}
}
command.text = buf.toString();
} catch (BadLocationException excp) {
ProcessingLog.logError(excp);
}
}
/**
* Set the indent of a bracket based on the command provided in the supplied document.
* @param document - the document being parsed
* @param command - the command being performed
*/
protected void smartInsertAfterBracket(IDocument document, DocumentCommand command) {
if (command.offset == -1 || document.getLength() == 0)
return;
try {
int p= (command.offset == document.getLength() ? command.offset - 1 : command.offset);
int line= document.getLineOfOffset(p);
int start= document.getLineOffset(line);
int whiteend= findEndOfWhiteSpace(document, start, command.offset);
// shift only when line does not contain any text up to the closing bracket
if (whiteend == command.offset) {
// evaluate the line with the opening bracket that matches out closing bracket
int indLine= findMatchingOpenBracket(document, line, command.offset, 1);
if (indLine != -1 && indLine != line) {
// take the indent of the found line
StringBuffer replaceText= new StringBuffer(getIndentOfLine(document, indLine));
// add the rest of the current line including the just added close bracket
replaceText.append(document.get(whiteend, command.offset - whiteend));
replaceText.append(command.text);
// modify document command
command.length= command.offset - start;
command.offset= start;
command.text= replaceText.toString();
}
}
} catch (BadLocationException excp) {
ProcessingLog.logError(excp);
}
}
}

View File

@@ -0,0 +1,202 @@
package processing.plugin.ui.processingeditor.language;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.EndOfLineRule;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WhitespaceRule;
import org.eclipse.jface.text.rules.WordRule;
import org.osgi.framework.Bundle;
import processing.plugin.ui.ProcessingPlugin;
import processing.plugin.ui.processingeditor.util.ProcessingColorProvider;
import processing.plugin.ui.processingeditor.util.ProcessingWhitespaceDetector;
import processing.plugin.ui.processingeditor.util.ProcessingWordDetector;
public class ProcessingCodeScanner extends RuleBasedScanner {
private static String keywordsFile = "Resources" + File.separatorChar + "keywords.txt"; // name of the syntax highlighting file
private static String[] fgKeywords1; // keywords (new, return, super)
private static String[] fgKeywords2; // PDE methods (setup, random, size)
private static String[] fgKeywords3; // primitive types (int, color, float)
private static String[] fgLiterals1; // static tokens (null, true, P2D)
private static String[] fgLiterals2; // environmental variables (mouseX, width, pixels)
private static String[] fgLabels; // possibly unused? Supporting them here because Processing does.
private static String[] fgOperators; // mathematical operators (+, -, *)
private static String[] fgInvalids; // possibly unused? Supporting them here because Processing does.
/*
* Static initialization block to load Processing Keywords in from keywordsFile
*
* Reads in the keyword file and splits each line at the tabs. The first string
* is the symbol, the second is the category (empty strings indicate operators)
* and the third string is ignored. In Processing it is used to lookup reference
* HTML.
*
* A HashMap stores each category as a key corresponding to a HashSet value that
* holds the tokens belonging to each category. After the keywordsFile is
* processed, the HashMap sets are converted to string arrays and loaded into the
* static keyword lists the document expects. Each set of keywords in the rules are
* explicitly listed. If the keyword categories change in the future, there will
* need to be new categories introduced in the code scanner and ProcessingColorProvider.
*
* Unexpected categories in the keywords file will be silently ignored.
* 'Unsure of how to JavaDoc an init block so I did this' - [lonnen] june 16, 2010
* Updated - [lonnen] august 12, 2010
*/
static {
HashMap<String, Set> KeywordMap = new HashMap<String, Set>();
//Read in the values
try{
InputStream is = getFileInputStream(keywordsFile);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader reader = new BufferedReader(isr);
String line = null;
while ((line = reader.readLine()) != null){
String pieces[] = line.split("\t"); // split the string at \t
if (pieces.length >= 2) {
String token = pieces[0].trim(); // ex. PVector
String coloring = pieces[1].trim(); // ex. KEWORD1
//String reference = pieces[2].trim(); // used for reference in PDE, unused here
if (coloring.isEmpty()) // catches operators
coloring = "OPERATOR";
if (KeywordMap.containsKey(coloring)){
KeywordMap.get(coloring).add(token);
} else {
Set<String> tokenSet = new HashSet<String>();
tokenSet.add(token);
KeywordMap.put(coloring, tokenSet);
}
//System.out.println(coloring + " " + token); // to print out a list of what is added
}
}
}
catch (Exception e){
e.printStackTrace();
}
try{
fgKeywords1= KeywordMap.containsKey("KEYWORD1") ? (String[]) KeywordMap.get("KEYWORD1").toArray(new String[KeywordMap.get("KEYWORD1").size()]) : new String[] {"test"};
fgKeywords2= KeywordMap.containsKey("KEYWORD2") ? (String[]) KeywordMap.get("KEYWORD2").toArray(new String[KeywordMap.get("KEYWORD2").size()]) : new String[] {};
fgKeywords3= KeywordMap.containsKey("KEYWORD3") ? (String[]) KeywordMap.get("KEYWORD3").toArray(new String[KeywordMap.get("KEYWORD3").size()]) : new String[] {};
fgLiterals1= KeywordMap.containsKey("LITERAL1") ? (String[]) KeywordMap.get("LITERAL1").toArray(new String[KeywordMap.get("LITERAL1").size()]) : new String[] {};
fgLiterals2= KeywordMap.containsKey("LITERAL2") ? (String[]) KeywordMap.get("LITERAL2").toArray(new String[KeywordMap.get("LITERAL2").size()]) : new String[] {};
fgLabels = KeywordMap.containsKey("LABELS") ? (String[]) KeywordMap.get("LABELS").toArray(new String[KeywordMap.get("LABELS").size()]) : new String[] {}; //unused?
fgOperators = KeywordMap.containsKey("OPERATOR") ? (String[]) KeywordMap.get("OPERATOR").toArray(new String[KeywordMap.get("OPERATOR").size()]) : new String[] {};
fgInvalids = KeywordMap.containsKey("INVALIDS") ? (String[]) KeywordMap.get("INVALIDS").toArray(new String[KeywordMap.get("INVALIDS").size()]) : new String[] {}; // unused?
}
catch (Exception e){
e.printStackTrace();
}
}
/**
* Creates a Processing code scanner with the given color provider.
*
* @param provider the color provider
*/
public ProcessingCodeScanner(ProcessingColorProvider provider) {
IToken keyword1= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.KEYWORD1)));
IToken keyword2= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.KEYWORD2)));
IToken keyword3= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.KEYWORD3)));
IToken literal1= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.LITERAL1)));
IToken literal2= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.LITERAL2)));
IToken label= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.LABEL)));
IToken operator= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.OPERATOR)));
IToken invalid= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.INVALID)));
// leave the rest for now
IToken string= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.STRING)));
IToken comment= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.COMMENT2)));
IToken other= new Token(new TextAttribute(provider.getColor(ProcessingColorProvider.DEFAULT)));
List rules= new ArrayList();
// Add rule for single line comments.
rules.add(new EndOfLineRule("//", comment)); //$NON-NLS-1$
// Add rule for strings and character constants.
rules.add(new SingleLineRule("\"", "\"", string, '\\')); //$NON-NLS-2$ //$NON-NLS-1$
rules.add(new SingleLineRule("'", "'", string, '\\')); //$NON-NLS-2$ //$NON-NLS-1$
// Add generic whitespace rule.
rules.add(new WhitespaceRule(new ProcessingWhitespaceDetector()));
// Add a rule for each keyword explaining how to color it
WordRule wordRule= new WordRule(new ProcessingWordDetector(), other);
for (int i= 0; i < fgKeywords1.length; i++)
wordRule.addWord(fgKeywords1[i], keyword1);
for (int i= 0; i < fgKeywords2.length; i++)
wordRule.addWord(fgKeywords2[i], keyword2);
for (int i= 0; i < fgKeywords3.length; i++)
wordRule.addWord(fgKeywords3[i], keyword3);
for (int i= 0; i < fgLiterals1.length; i++)
wordRule.addWord(fgLiterals1[i], literal1);
for (int i= 0; i < fgLiterals2.length; i++)
wordRule.addWord(fgLiterals2[i], literal2);
for (int i= 0; i < fgLabels.length; i++)
wordRule.addWord(fgLabels[i], label);
for (int i= 0; i < fgOperators.length; i++)
wordRule.addWord(fgOperators[i], operator);
for (int i= 0; i < fgInvalids.length; i++)
wordRule.addWord(fgInvalids[i], invalid);
// Set these as the colorizing rules for the document
rules.add(wordRule);
IRule[] result= new IRule[rules.size()];
rules.toArray(result);
setRules(result);
}
/**
* Concatenates the keyword arrays into one array and returns it.
*
* @return A string array of all the keywords
*/
public final static String[] getKeywords(){
String[] result = new String[fgKeywords1.length + fgKeywords2.length + fgKeywords3.length];
System.arraycopy(fgKeywords1, 0, result, 0, fgKeywords1.length);
System.arraycopy(fgKeywords2, 0, result, fgKeywords1.length, fgKeywords2.length);
System.arraycopy(fgKeywords3, 0, result, fgKeywords1.length+fgKeywords2.length, fgKeywords3.length);
return result;
}
/**
* Returns a buffered input stream for a file in the plug-in directory.
*
* @param filename the file to be loaded
* @return BufferedInputStream to read the file with
*/
public static BufferedInputStream getFileInputStream(String filename) {
Bundle bundle = ProcessingPlugin.getDefault().getBundle();
URL fileLocation;
try {
fileLocation = FileLocator.toFileURL(bundle.getEntry(filename));
BufferedInputStream file = new BufferedInputStream(fileLocation.openStream());
return file;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,80 @@
package processing.plugin.ui.processingeditor.language;
import java.text.MessageFormat;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.contentassist.ContextInformation;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationPresenter;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
/**
* Naive auto completion provider.
* <p>
* None of this is particularly useful, but may be extended in the future.
*
*
* @author lonnen
*/
public class ProcessingCompletionProcessor implements IContentAssistProcessor {
/** Utility class with methods to determine if the context tip window is still valid */
protected static class Validator implements IContextInformationValidator, IContextInformationPresenter {
protected int fInstallOffset;
/* @see IContextInformationValidator#isContextInformationValid(int) */
public boolean isContextInformationValid(int offset) { return Math.abs(fInstallOffset - offset) < 5; }
/* @see IContextInformationValidator#install(IContextInformation, ITextViewer, int) */
public void install(IContextInformation info, ITextViewer viewer, int offset) { fInstallOffset= offset; }
/* @see org.eclipse.jface.text.contentassist.IContextInformationPresenter#updatePresentation(int, TextPresentation) */
public boolean updatePresentation(int documentPosition, TextPresentation presentation) { return false; }
}
/** Strings to populate the context assistance pop up box. */
protected final static String[] fgProposals = {};
/** Validates the context tip window. */
protected IContextInformationValidator fValidator= new Validator();
/* Method declared on IContentAssistProcessor */
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int documentOffset) {
ICompletionProposal[] result= new ICompletionProposal[fgProposals.length];
return result; // not useful
}
/* Method declared on IContentAssistProcessor */
public IContextInformation[] computeContextInformation(ITextViewer viewer, int documentOffset) {
IContextInformation[] result= new IContextInformation[5];
for (int i= 0; i < result.length; i++)
result[i]= new ContextInformation(
MessageFormat.format("proposal {0} at position {1}", new Object[] { new Integer(i), new Integer(documentOffset) }),
MessageFormat.format("proposal {0} valid from {1} to {2}", new Object[] { new Integer(i), new Integer(documentOffset - 5), new Integer(documentOffset + 5)}));
return result; }
/* Method declared on IContentAssistProcessor */
public char[] getCompletionProposalAutoActivationCharacters() {
return new char[] { '.', '(' };
}
/* Method declared on IContentAssistProcessor */
public char[] getContextInformationAutoActivationCharacters() {
return new char[] { '#' };
}
/* Method declared on IContentAssistProcessor */
public IContextInformationValidator getContextInformationValidator() {
return fValidator;
}
/* Method declared on IContentAssistProcessor */
public String getErrorMessage() {
return null;
}
}

View File

@@ -0,0 +1,234 @@
package processing.plugin.ui.processingeditor.language;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextDoubleClickStrategy;
import org.eclipse.jface.text.ITextViewer;
/**
* On a double click, try to automatically highlight a logical section of the source code.
* <p>
* If the double click is near an opening or closing bracket type, this should highlight to
* the other bracket in the set.
* </p>
* <p>
* If the user didn't double click a bracket, assume they wanted the word
*
* @author lonnen
*/
public class ProcessingDoubleClickSelector implements ITextDoubleClickStrategy {
protected ITextViewer fText;
protected int fPos;
protected int fStartPos;
protected int fEndPos;
/** Supported bracket types */
protected static char[] fgBrackets= { '{', '}', '(', ')', '[', ']', '"', '"', '<', '>'};
/** Boring ol' constructor */
public ProcessingDoubleClickSelector() { super(); }
/* Method declared on ITextDoubleClickStrategy */
public void doubleClicked(ITextViewer text) {
fPos = text.getSelectedRange().x;
if (fPos < 0)
return;
fText= text;
// If a matching bracket wasn't found, assume the user wants the word
if (!selectBracketBlock())
selectWord();
}
/**
* Match the brackets at the current selection.
* Return <code>true</code> if successful, <code>false</code> otherwise.
*
* @return <code>true</code> if brackets match, <code>false</code> otherwise
*/
protected boolean matchBracketsAt() {
char prevChar, nextChar;
int i;
int bracketIndex1= fgBrackets.length;
int bracketIndex2= fgBrackets.length;
fStartPos= -1;
fEndPos= -1;
// get the chars preceding and following the start position
try {
IDocument doc= fText.getDocument();
prevChar= doc.getChar(fPos - 1);
nextChar= doc.getChar(fPos);
// is the char either an open or close bracket?
for (i= 0; i < fgBrackets.length; i= i + 2) {
if (prevChar == fgBrackets[i]) {
fStartPos= fPos - 1;
bracketIndex1= i;
}
}
for (i= 1; i < fgBrackets.length; i= i + 2) {
if (nextChar == fgBrackets[i]) {
fEndPos= fPos;
bracketIndex2= i;
}
}
if (fStartPos > -1 && bracketIndex1 < bracketIndex2) {
fEndPos= searchForClosingBracket(fStartPos, prevChar, fgBrackets[bracketIndex1 + 1], doc);
if (fEndPos > -1)
return true;
fStartPos= -1;
} else if (fEndPos > -1) {
fStartPos= searchForOpenBracket(fEndPos, fgBrackets[bracketIndex2 - 1], nextChar, doc);
if (fStartPos > -1)
return true;
fEndPos= -1;
}
} catch (BadLocationException x) {
// assume everything is cool
}
return false;
}
/**
* Select the word at the current selection location. Return <code>true</code> if successful,
* <code>false</code> otherwise.
*
* @return <code>true</code> if a word can be found at the current selection location, <code>false</code> otherwise
*/
protected boolean matchWord() {
IDocument doc= fText.getDocument();
try {
int pos= fPos;
char c;
while (pos >= 0) {
c= doc.getChar(pos);
if (!Character.isJavaIdentifierPart(c))
break;
--pos;
}
fStartPos= pos;
pos= fPos;
int length= doc.getLength();
while (pos < length) {
c= doc.getChar(pos);
if (!Character.isJavaIdentifierPart(c))
break;
++pos;
}
fEndPos= pos;
return true;
} catch (BadLocationException x) {
//assume everything's cool
}
return false;
}
/**
* Returns the position of the closing bracket after <code>startPosition</code>.
*
* @param startPosition - the beginning position
* @param openBracket - the character that represents the open bracket
* @param closeBracket - the character that represents the close bracket
* @param document - the document being searched
* @return the location of the closing bracket.
* @throws BadLocationException in case <code>startPosition</code> is invalid in the document
*/
protected int searchForClosingBracket(int startPosition, char openBracket, char closeBracket, IDocument document) throws BadLocationException {
int stack= 1;
int closePosition= startPosition + 1;
int length= document.getLength();
char nextChar;
while (closePosition < length && stack > 0) {
nextChar= document.getChar(closePosition);
if (nextChar == openBracket && nextChar != closeBracket)
stack++;
else if (nextChar == closeBracket)
stack--;
closePosition++;
}
if (stack == 0)
return closePosition - 1;
return -1;
}
/**
* Returns the position of the open bracket before <code>startPosition</code>.
*
* @param startPosition - the beginning position
* @param openBracket - the character that represents the open bracket
* @param closeBracket - the character that represents the close bracket
* @param document - the document being searched
* @return the location of the starting bracket.
* @throws BadLocationException in case <code>startPosition</code> is invalid in the document
*/
protected int searchForOpenBracket(int startPosition, char openBracket, char closeBracket, IDocument document) throws BadLocationException {
int stack= 1;
int openPos= startPosition - 1;
char nextChar;
while (openPos >= 0 && stack > 0) {
nextChar= document.getChar(openPos);
if (nextChar == closeBracket && nextChar != openBracket)
stack++;
else if (nextChar == openBracket)
stack--;
openPos--;
}
if (stack == 0)
return openPos + 1;
return -1;
}
/**
* Select the area between the selected bracket and the closing bracket.
*
* @return <code>true</code> if selection was successful, <code>false</code> otherwise
*/
protected boolean selectBracketBlock() {
if (matchBracketsAt()) {
if (fStartPos == fEndPos)
fText.setSelectedRange(fStartPos, 0);
else
fText.setSelectedRange(fStartPos + 1, fEndPos - fStartPos - 1);
return true;
}
return false;
}
/**
* Select the word at the current selection.
*/
protected void selectWord() {
if (matchWord()) {
if (fStartPos == fEndPos)
fText.setSelectedRange(fStartPos, 0);
else
fText.setSelectedRange(fStartPos + 1, fEndPos - fStartPos - 1);
}
}
}

View File

@@ -0,0 +1,51 @@
package processing.plugin.ui.processingeditor.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
/** Manages the colors used in the Java Editor */
public class ProcessingColorProvider {
public static final RGB COMMENT1= new RGB(126, 126, 126); //comment 1
public static final RGB COMMENT2= new RGB(126, 126, 126); //comment 2
public static final RGB KEYWORD1= new RGB(204, 102, 0);
public static final RGB KEYWORD2= new RGB(204, 102, 0);
public static final RGB KEYWORD3= new RGB(204, 102, 0);
public static final RGB LITERAL1= new RGB(0, 102, 153); // currently unused [lonnen] june 16, 2010
public static final RGB LITERAL2= new RGB(0, 102, 153);
public static final RGB LABEL= new RGB(0, 0, 128); // use listed as '?' in p5 doc
public static final RGB OPERATOR= new RGB(0, 0, 0);
public static final RGB INVALID= new RGB(126, 126, 126); // never used [lonnen] june 16, 2010
public static final RGB STRING= new RGB(0, 102, 153);
public static final RGB DEFAULT= new RGB(0, 0, 0);
protected Map fColorTable= new HashMap(17);
/** Release all of the color resources held onto by the receiver. */
public void dispose() {
Iterator e= fColorTable.values().iterator();
while (e.hasNext())
((Color) e.next()).dispose();
}
/**
* convert an RGB value to a Color using the resource table
*
* @param rgb the RGB value
* @return the color stored in the color table for the given RGB value
*/
public Color getColor(RGB rgb) {
Color color= (Color) fColorTable.get(rgb);
if (color == null) {
color= new Color(Display.getCurrent(), rgb);
fColorTable.put(rgb, color);
}
return color;
}
}

View File

@@ -0,0 +1,11 @@
package processing.plugin.ui.processingeditor.util;
import org.eclipse.jface.text.rules.IWhitespaceDetector;
public class ProcessingWhitespaceDetector implements IWhitespaceDetector {
public boolean isWhitespace(char character){
return Character.isWhitespace(character);
}
}

View File

@@ -0,0 +1,18 @@
package processing.plugin.ui.processingeditor.util;
import org.eclipse.jface.text.rules.IWordDetector;
/**
* Processing Words are Java words, so this class wraps Character methods to detect
* Java words. JavaDoc for the methods can be found in the IWordDetector interface.
* */
public class ProcessingWordDetector implements IWordDetector {
public boolean isWordPart(char character) {
return Character.isJavaIdentifierPart(character);
}
public boolean isWordStart(char character) {
return Character.isJavaIdentifierStart(character);
}
}

View File

@@ -0,0 +1,174 @@
package processing.plugin.ui.wizards;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import processing.plugin.core.builder.SketchNature;
import processing.plugin.ui.wizards.NewSketchWizardPage;
/**
* A wizard to create a new Processing sketch project.
* <p>
* Contains a single page the requests the name of the sketch
* and the sketch book folder to put the sketch in.
*
* @author lonnen
*/
public class NewSketchWizard extends Wizard implements INewWizard {
/** The single page in the wizard */
private NewSketchWizardPage page;
/** The project to be created */
private IProject project;
/** Constructor */
public NewSketchWizard(){
// IDialogSettings processingSettings = ProcessingEditorPlugin.getDefault().getDialogSettings();
// IDialogSettings wizardSettings = processingSettings.getSection("NewSketchWizard");
// if (wizardSettings == null)
// wizardSettings = processingSettings.addNewSection("NewSketchWizard");
// setDialogSettings(processingSettings);
}
public void init(IWorkbench workbench, IStructuredSelection selection) {
// init code
}
/**
* When the finish button is called, create the project
* <p>
* The wizard page ensures that the user cannot click finish
* without a valid directory and sketch name. The creation of the
* project is wrapped in a runnable object so it can be monitored
* and potentially canceled by the user.
*/
public boolean performFinish() {
final String sketchName = page.getSketchName();
final IPath sketchbookPath = page.getSketchbookLoc().append(sketchName);
project = ResourcesPlugin.getWorkspace().getRoot().getProject(sketchName);
final IProjectDescription sketchDescription = ResourcesPlugin.getWorkspace().newProjectDescription(project.getName());
sketchDescription.setLocation(sketchbookPath);
WorkspaceModifyOperation op = new WorkspaceModifyOperation(){
protected void execute(IProgressMonitor monitor) throws CoreException{
createNewProject(sketchDescription, project, monitor);
}
};
try{
getContainer().run(true, true, op);
} catch (InvocationTargetException e){
Throwable realException = e.getTargetException();
MessageDialog.openError(getShell(), "Error!", realException.getMessage());
return false;
} catch (InterruptedException e){ // "shut it down"
return false;
}
return true;
}
/**
* Creates the project in the workspace
*
* @param description
* @param proj
* @param monitor
* @throws CoreException
* @throws OperationCanceledException
*/
protected void createNewProject(IProjectDescription description, IProject proj,
IProgressMonitor monitor) throws CoreException, OperationCanceledException{
monitor.beginTask("Creating a new Sketch", 500);
try{
// create the project root
proj.create(description, new SubProgressMonitor(monitor, 100));
proj.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 100));
if (monitor.isCanceled()){throw new OperationCanceledException();}
// create the folders
IContainer container = (IContainer) proj;
//data
IFolder dataFolder = container.getFolder(new Path("data"));
dataFolder.create(true, true, monitor);
monitor.worked(33);
//code
IFolder codeFolder = container.getFolder(new Path("code"));
codeFolder.create(true, true, monitor);
monitor.worked(33);
//bin
IFolder binFolder = container.getFolder(new Path("bin"));
binFolder.create(true, true, monitor);
monitor.worked(34);
if (monitor.isCanceled()){throw new OperationCanceledException();}
//create the main file
addFileToContainer(container, new Path(project.getName() + ".pde"), null, new SubProgressMonitor(monitor, 100));
} finally{
monitor.done();
}
// this is a new project
SketchNature.addNature(proj);
}
/**
* Adds a new file to a project
*
* @param container where to add the file
* @param path the path to the file
* @param contentStream what to put in the file
* @param monitor report the progress back to the user
* @throws CoreException if there are problems with the resource
*/
private void addFileToContainer(IContainer container, Path path, InputStream contentStream, IProgressMonitor monitor) throws CoreException{
final IFile file = container.getFile(path);
if (contentStream == null){
contentStream = new ByteArrayInputStream(" void setup(){} \n void draw(){} ".getBytes());
}
if(file.exists()){
file.setContents(contentStream, true, true, monitor);
} else{
file.create(contentStream, true, monitor);
}
monitor.done();
}
/**
* Initializes the single page and adds it to the wizard.
*/
public void addPages(){
setWindowTitle("New Sketch Wizard");
page = new NewSketchWizardPage();
addPage(page);
}
}

View File

@@ -0,0 +1,210 @@
package processing.plugin.ui.wizards;
import java.io.File;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import processing.app.Sketch;
public class NewSketchWizardPage extends WizardPage {
/** field containing the name of the sketch to be created */
private Text sketchNameField;
/** field containing path of the sketch book folder that will contain the sketch */
private Text sketchbookPathField; // TODO get the default from the plugin preferences
/** the actual path of the sketch book folder */
private IPath initialSketchbookPath;
public NewSketchWizardPage() {
super("selectFiles");
setTitle("New Sketch Wizard");
setDescription("Create a new Processing Sketch");
}
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
final GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 3;
container.setLayout(gridLayout);
setControl(container);
final Label label = new Label(container, SWT.NONE);
final GridData gridData = new GridData();
gridData.horizontalSpan = 3;
label.setLayoutData(gridData);
label.setText("Select a name for the new sketch.");
final Label label_1 = new Label(container, SWT.NONE);
final GridData gridData_1 = new GridData(GridData.HORIZONTAL_ALIGN_END);
label_1.setLayoutData(gridData_1);
label_1.setText("Sketch Name:");
sketchNameField = new Text(container, SWT.BORDER);
sketchNameField.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e){
updatePageComplete();
}
});
sketchNameField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
final Label label_2 = new Label(container, SWT.NONE);
final GridData gridData_2 = new GridData();
gridData_2.horizontalSpan = 3;
label_2.setLayoutData(gridData_2);
final Label label_3 = new Label(container, SWT.NONE);
final GridData gridData_3 = new GridData();
gridData_3.horizontalSpan = 3;
label_3.setLayoutData(gridData_3);
label_3.setLayoutData(gridData_3);
label_3.setText("Select the sketchbook folder that will contain the sketch:");
final Label label_4 = new Label(container, SWT.NONE);
final GridData gridData_4 = new GridData();
gridData_4.horizontalIndent = 20;
label_4.setLayoutData(gridData_4);
label_4.setText("Sketchbook Folder:");
sketchbookPathField = new Text(container, SWT.BORDER);
sketchbookPathField.addModifyListener(
new ModifyListener() {
public void modifyText(ModifyEvent e){
updatePageComplete();
}
});
sketchbookPathField.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
final Button button_1 = new Button(container, SWT.NONE);
button_1.addSelectionListener(new SelectionAdapter(){
public void widgetSelected(SelectionEvent e){browseForDestinationFolder();
}
});
button_1.setText("Browse...");
initContents();
}
/** initialize the contents of the sketch book path */
private void initContents() {
if (initialSketchbookPath == null){
setPageComplete(false);
return;
}
// see if the sketchbook path has been set in the prefs
// IPreferenceStore prefs = ProcessingEditorPlugin.getDefault().getPreferenceStore();
//
// IPath rootLoc = ResourcesPlugin.getWorkspace().getRoot().getLocation();
}
/**
* Browse button functionality to find a destination folder
* <p>
* Prettifies the file path if it happens to be in the workspace
*/
protected void browseForDestinationFolder() {
IPath path = browse(getSketchbookLoc());
if (path == null)
return;
IPath rootLoc = ResourcesPlugin.getWorkspace().getRoot().getLocation();
if (rootLoc.isPrefixOf(path))
path = path.setDevice(null).removeFirstSegments(rootLoc.segmentCount());
sketchbookPathField.setText(path.toString());
}
/**
* Sets up a dialog box allowing you to select a directory to use for the sketchbook
*
* @param path the path to be investigated
* @return the path chosen in the dialog box
*/
private IPath browse(IPath path){
DirectoryDialog dialog = new DirectoryDialog(getShell());
if (path != null){
if(path.segmentCount() > 1)
dialog.setFilterPath(path.toOSString());
}
String result = dialog.open();
if (result == null)
return null;
return new Path(result);
}
/**
* Verifies that the sketchbook path exists and does not already contain a sketch with that name.
*/
private void updatePageComplete() {
setPageComplete(false);
// check the sketchbook path first
IPath sketchbookLoc = getSketchbookLoc();
if (sketchbookLoc == null || !sketchbookLoc.toFile().exists()){
setMessage(null);
setErrorMessage("Please specify a sketchbook folder.");
return;
}
// ensure the sketch isn't already present in the sketchbook
String sketchName = getSketchName();
for( File child : sketchbookLoc.toFile().listFiles()){
if (child.isDirectory() && child.getName().equals(sketchName)){
setMessage(null);
setErrorMessage("A sketch with that name already exists. Please choose another.");
return;
}
}
// if nothing was caught, enable the finish button
setPageComplete(true);
setMessage(null);
setErrorMessage(null);
}
/**
* Sanitizes and returns the sketchNameField contents
* <p>
* Uses the Processing sanitize method to ensure consistent naming
* rules across editors.
*
* @return the contents of the sketchNameField as a string or null
*/
protected String getSketchName() {
String text = sketchNameField.getText().trim();
if (text.length() == 0)
return null;
text = Sketch.sanitizeName(text);
return text;
}
/**
* Tries to resolve the contents of the sketch book path field to an IPath, and returns it.
* If the field contains a relative path it will be resolved relative to the Eclipse workspace folder.
*
* @return an absolute IPath handle to the contents of the sketchbookPathField or null
*/
protected IPath getSketchbookLoc() {
String text = sketchbookPathField.getText().trim();
if (text.length() == 0)
return null;
IPath path = new Path(text);
if (!path.isAbsolute()) // relative paths are relative to the Eclipse workspace
path = ResourcesPlugin.getWorkspace().getRoot().getLocation().append(path);
return path;
}
}