package tk.eclipse.plugin.jseditor.editors;
import java.util.ArrayList;
import java.util.Stack;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.MatchingCharacterPainter;
import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.ChainedPreferenceStore;
import org.eclipse.ui.texteditor.ContentAssistAction;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import tk.eclipse.plugin.htmleditor.ColorProvider;
import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.HTMLProjectParams;
import tk.eclipse.plugin.htmleditor.editors.FoldingInfo;
import tk.eclipse.plugin.htmleditor.editors.SoftTabVerifyListener;
/**
* The JavaScript editor.
*
* @author Naoki Takezoe
* @see tk.eclipse.plugin.jseditor.editors.JavaScriptOutlinePage
* @see tk.eclipse.plugin.jseditor.editors.JavaScriptConfiguration
* @see tk.eclipse.plugin.jseditor.editors.JavaScriptAssistProcessor
* @see tk.eclipse.plugin.jseditor.editors.JavaScriptCharacterPairMatcher
* @see tk.eclipse.plugin.jseditor.editors.JavaScriptHyperlinkDetector
*/
public class JavaScriptEditor extends TextEditor {
private ColorProvider colorProvider;
private SoftTabVerifyListener softTabListener;
private JavaScriptOutlinePage outline;
private JavaScriptCharacterPairMatcher pairMatcher;
private ProjectionSupport fProjectionSupport;
private JavaScriptHyperlinkDetector hyperlinkDetector;
public static final String GROUP_JAVASCRIPT = "_javascript";
public static final String ACTION_COMMENT = "_comment";
/**
* The constructor.
*/
public JavaScriptEditor(){
super();
colorProvider = HTMLPlugin.getDefault().getColorProvider();
setSourceViewerConfiguration(new JavaScriptConfiguration(colorProvider));
setPreferenceStore(new ChainedPreferenceStore(
new IPreferenceStore[]{
getPreferenceStore(),
HTMLPlugin.getDefault().getPreferenceStore()
}));
outline = new JavaScriptOutlinePage(this);
IPreferenceStore store = HTMLPlugin.getDefault().getPreferenceStore();
softTabListener = new SoftTabVerifyListener();
softTabListener.setUseSoftTab(store.getBoolean(HTMLPlugin.PREF_USE_SOFTTAB));
softTabListener.setSoftTabWidth(store.getInt(HTMLPlugin.PREF_SOFTTAB_WIDTH));
setAction(ACTION_COMMENT,new CommentAction());
}
@Override
protected void updateSelectionDependentActions() {
super.updateSelectionDependentActions();
ITextSelection sel = (ITextSelection)getSelectionProvider().getSelection();
if(sel.getText().equals("")){
getAction(ACTION_COMMENT).setEnabled(false);
} else {
getAction(ACTION_COMMENT).setEnabled(true);
}
}
@Override
protected ISourceViewer createSourceViewer(Composite parent,IVerticalRuler ruler, int styles) {
ISourceViewer viewer= new ProjectionViewer(parent, ruler, getOverviewRuler(), true, styles);
getSourceViewerDecorationSupport(viewer);
viewer.getTextWidget().addVerifyListener(softTabListener);
return viewer;
}
@Override
protected final void editorContextMenuAboutToShow(IMenuManager menu) {
super.editorContextMenuAboutToShow(menu);
menu.add(new Separator(GROUP_JAVASCRIPT));
addAction(menu, GROUP_JAVASCRIPT, ACTION_COMMENT);
}
@Override
protected void doSetInput(IEditorInput input) throws CoreException {
if(input instanceof IFileEditorInput){
setDocumentProvider(new JavaScriptTextDocumentProvider());
} else if(input instanceof IStorageEditorInput){
setDocumentProvider(new JavaScriptFileDocumentProvider());
} else {
setDocumentProvider(new JavaScriptTextDocumentProvider());
}
super.doSetInput(input);
}
@Override
public void doSave(IProgressMonitor progressMonitor) {
super.doSave(progressMonitor);
update();
}
@Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
ProjectionViewer viewer = (ProjectionViewer)getSourceViewer();
fProjectionSupport = new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors());
fProjectionSupport.install();
viewer.doOperation(ProjectionViewer.TOGGLE);
updateFolding();
StyledText widget = viewer.getTextWidget();
widget.setTabs(
getPreferenceStore().getInt(
AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH));
widget.addVerifyListener(softTabListener);
ITextViewerExtension2 extension= (ITextViewerExtension2) getSourceViewer();
pairMatcher = new JavaScriptCharacterPairMatcher();
pairMatcher.setEnable(getPreferenceStore().getBoolean(HTMLPlugin.PREF_PAIR_CHAR));
MatchingCharacterPainter painter = new MatchingCharacterPainter(getSourceViewer(), pairMatcher);
painter.setColor(Display.getDefault().getSystemColor(SWT.COLOR_GRAY));
extension.addPainter(painter);
hyperlinkDetector = new JavaScriptHyperlinkDetector();
viewer.setHyperlinkDetectors(new IHyperlinkDetector[]{hyperlinkDetector}, SWT.CTRL);
update();
}
/**
* Updates internal status.
*
* <ol>
* <li>updates the outline view</li>
* <li>updates the foldings</li>
* <li>validates contents if the editor input is <code>IFileEditorInput</code></li>
* <li>updates assist information if the editor input is <code>IFileEditorInput</code></li>
* <li>updates hyperlink information if the editor input is <code>IFileEditorInput</code></li>
* </ol>
*/
protected void update(){
outline.update();
updateFolding();
if(getEditorInput() instanceof IFileEditorInput){
doValidate();
IFileEditorInput input = (IFileEditorInput)getEditorInput();
JavaScriptConfiguration config = (JavaScriptConfiguration)getSourceViewerConfiguration();
config.getAssistProcessor().update(input.getFile());
hyperlinkDetector.update(input.getFile());
}
}
/**
* Invokes <code>JavaScriptValidator</code> to validate editing source code.
*
* @see JavaScriptValidator
*/
protected void doValidate(){
try {
ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
try {
IFileEditorInput input = (IFileEditorInput)getEditorInput();
new JavaScriptValidator(input.getFile()).doValidate();
} catch(Exception ex){
}
}
},null);
} catch(Exception ex){
HTMLPlugin.logException(ex);
}
}
@Override
public void dispose() {
if(getEditorInput() instanceof IFileEditorInput){
try {
ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
try {
IFileEditorInput input = (IFileEditorInput)getEditorInput();
HTMLProjectParams params = new HTMLProjectParams(input.getFile().getProject());
if(params.getRemoveMarkers()){
input.getFile().deleteMarkers(IMarker.PROBLEM,false,0);
}
} catch(Exception ex){
}
}
}, null);
} catch(Exception ex){
}
}
pairMatcher.dispose();
super.dispose();
}
@Override
public void doSaveAs() {
super.doSaveAs();
update();
}
@Override
protected boolean affectsTextPresentation(PropertyChangeEvent event) {
return super.affectsTextPresentation(event) || colorProvider.affectsTextPresentation(event);
}
@Override
protected void handlePreferenceStoreChanged(PropertyChangeEvent event){
colorProvider.handlePreferenceStoreChanged(event);
// updateAssistProperties(event);
String key = event.getProperty();
if(key.equals(HTMLPlugin.PREF_PAIR_CHAR)){
boolean enable = ((Boolean)event.getNewValue()).booleanValue();
pairMatcher.setEnable(enable);
}
super.handlePreferenceStoreChanged(event);
softTabListener.preferenceChanged(event);
}
@Override
protected void createActions() {
super.createActions();
// Add a content assist action
IAction action = new ContentAssistAction(
HTMLPlugin.getDefault().getResourceBundle(),"ContentAssistProposal", this);
action.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
setAction("ContentAssistProposal", action);
}
@Override
public Object getAdapter(Class adapter){
if(IContentOutlinePage.class.equals(adapter)){
return outline;
}
return super.getAdapter(adapter);
}
/**
* Update folding informations.
*/
private void updateFolding(){
try {
ProjectionViewer viewer = (ProjectionViewer)getSourceViewer();
if(viewer==null){
return;
}
ProjectionAnnotationModel model = viewer.getProjectionAnnotationModel();
if(model==null){
return;
}
IDocument doc = getDocumentProvider().getDocument(getEditorInput());
String source = doc.get();
ArrayList<FoldingInfo> list = new ArrayList<FoldingInfo>();
Stack<FoldingInfo> stack = new Stack<FoldingInfo>();
FoldingInfo prev = null;
char quote = 0;
boolean escape = false;
for(int i=0;i<source.length();i++){
char c = source.charAt(i);
// skip string
if(quote!=0 && escape==true){
escape = false;
} else if((prev==null || !prev.getType().equals("comment")) && (c=='"' || c=='\'')){
if(quote==0){
quote = c;
} else if(quote == c){
quote = 0;
}
} else if(quote!=0 && (c=='\\')){
escape = true;
} else if(quote!=0 && (c=='\n' || c=='\r')){
quote = 0;
// start comment
} else if(c=='/' && source.length() > i+1 && quote==0){
if(source.charAt(i+1)=='*'){
prev = new FoldingInfo(i,-1,"comment");
stack.push(prev);
i++;
}
// end comment
} else if(c=='*' && source.length() > i+1 && !stack.isEmpty() && quote==0){
if(source.charAt(i+1)=='/' && prev.getType().equals("comment")){
FoldingInfo info = stack.pop();
if(doc.getLineOfOffset(info.getStart())!=doc.getLineOfOffset(i)){
list.add(new FoldingInfo(info.getStart(), i+2 + FoldingInfo.countUpLineDelimiter(source, i+2), "comment"));
}
prev = stack.isEmpty() ? null : (FoldingInfo)stack.get(stack.size()-1);
i++;
}
// open blace
} else if(c=='{' && quote==0){
if(prev==null || !prev.getType().equals("comment")){
if(findFunction(source, i)){
prev = new FoldingInfo(i, -1, "function");
} else {
prev = new FoldingInfo(i, -1, "blace");
}
stack.push(prev);
}
// close blace
} else if(c=='}' && prev!=null && !prev.getType().equals("comment") && quote==0){
FoldingInfo info = stack.pop();
if(info.getType().equals("function") && doc.getLineOfOffset(info.getStart())!=doc.getLineOfOffset(i)){
list.add(new FoldingInfo(info.getStart(), i+2 + FoldingInfo.countUpLineDelimiter(source, i+2), "function"));
}
prev = stack.isEmpty() ? null : (FoldingInfo)stack.get(stack.size()-1);
}
}
FoldingInfo.applyModifiedAnnotations(model, list);
} catch(Exception ex){
HTMLPlugin.logException(ex);
}
}
private boolean findFunction(String text, int pos){
text = text.substring(0, pos);
int index1 = text.lastIndexOf("function");
int index2 = text.lastIndexOf("{");
if(index1==-1){
return false;
} else if(index1 > index2){
return true;
} else {
return false;
}
}
/**
* The action to toggle comment out selection range.
*/
private class CommentAction extends Action {
public CommentAction(){
super(HTMLPlugin.getResourceString("JavaScriptEditor.CommentAction"));
setEnabled(false);
setAccelerator(SWT.CTRL | '/');
}
@Override
public void run() {
ITextSelection sel = (ITextSelection)getSelectionProvider().getSelection();
IDocument doc = getDocumentProvider().getDocument(getEditorInput());
String text = sel.getText();
text = text.replaceAll("[\r\n \t]+$", ""); // rtrim
int length = text.length();
try {
if(text.startsWith("//")){
text = text.replaceAll("(^|\r\n|\r|\n)//","$1");
} else {
text = text.replaceAll("(^|\r\n|\r|\n)","$1//");
}
doc.replace(sel.getOffset(), length, text);
} catch (BadLocationException e) {
HTMLPlugin.logException(e);
}
}
}
}