package org.jbehave.eclipse.editor.story;
import java.util.List;
import java.util.ResourceBundle;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.templates.DocumentTemplateContext;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContext;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateProposal;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.jbehave.eclipse.Activator;
import org.jbehave.eclipse.JBehaveProject;
import org.jbehave.eclipse.JBehaveProjectListener;
import org.jbehave.eclipse.JBehaveProjectRegistry;
import org.jbehave.eclipse.editor.EditorActionDefinitionIds;
import org.jbehave.eclipse.editor.EditorMessages;
import org.jbehave.eclipse.editor.step.LocalizedStepSupport;
import org.jbehave.eclipse.editor.step.StepCandidate;
import org.jbehave.eclipse.editor.step.StepJumper;
import org.jbehave.eclipse.editor.story.actions.FormatTableAction;
import org.jbehave.eclipse.editor.story.actions.JumpToDeclarationAction;
import org.jbehave.eclipse.editor.story.actions.QuickSearchAction;
import org.jbehave.eclipse.editor.story.actions.ShowOutlineAction;
import org.jbehave.eclipse.editor.story.actions.ToggleCommentAction;
import org.jbehave.eclipse.editor.story.completion.StoryContextType;
import org.jbehave.eclipse.editor.story.outline.OutlineModel;
import org.jbehave.eclipse.editor.story.outline.OutlineModelBuilder;
import org.jbehave.eclipse.editor.story.outline.OutlineView;
import org.jbehave.eclipse.editor.story.validator.MarkingStoryValidator;
import org.jbehave.eclipse.editor.text.ColorManager;
import org.jbehave.eclipse.editor.text.ProjectAwareFastPartitioner;
import org.jbehave.eclipse.editor.text.TemplateUtils;
import org.jbehave.eclipse.editor.text.TextAttributeProvider;
import org.jbehave.eclipse.editor.text.style.TextStyle;
import org.jbehave.eclipse.editor.text.style.TextStylePreferences;
import org.jbehave.eclipse.preferences.PreferenceConstants;
import org.jbehave.eclipse.util.Visitor;
import fj.Effect;
public class StoryEditor extends TextEditor {
public static final String EDITOR_ID = "org.jbehave.eclipse.editor.story.StoryEditor";
private ColorManager colorManager;
//
private FormatTableAction formatTable;
private ShowOutlineAction showOutline;
private JumpToDeclarationAction jumpToDeclaration;
private QuickSearchAction quickSearch;
private ToggleCommentAction toggleComment;
//
private IPropertyChangeListener listener;
private JBehaveProjectListener projectListener;
//
private TextAttributeProvider textAttributeProvider;
private Object outlineView;
private StoryConfiguration storyConfiguration;
public StoryEditor() {
colorManager = new ColorManager();
textAttributeProvider = new TextAttributeProvider(colorManager);
textAttributeProvider.changeTheme(getTheme());
storyConfiguration = new StoryConfiguration(this, textAttributeProvider);
setSourceViewerConfiguration(storyConfiguration);
setDocumentProvider(new StoryDocumentProvider());
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
super.init(site, input);
listener = new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
updateStyles();
}
};
getStore().addPropertyChangeListener(listener);
}
private void updateStyles() {
TextStyle theme = getTheme();
textAttributeProvider.changeTheme(theme);
StyledText textWidget = getSourceViewer().getTextWidget();
textWidget.setBackground(colorManager.getColor(theme.getBackgroundOrDefault()));
textWidget.setForeground(colorManager.getColor(theme.getForegroundOrDefault()));
adjustCurrentLineColor(theme);
getSourceViewer().invalidateTextPresentation();
}
private static TextStyle getTheme() {
return TextStylePreferences.getTheme(getStore());
}
@Override
public void dispose() {
getStore().removePropertyChangeListener(listener);
colorManager.dispose();
getJBehaveProject().removeListener(projectListener);
super.dispose();
}
@Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
updateStyles();
}
private static boolean AttemptToChangeCurrentLineColorAccordingToTheme = true;
private void adjustCurrentLineColor(TextStyle theme) {
if (AttemptToChangeCurrentLineColorAccordingToTheme) {
PreferenceConverter.setValue(//
getPreferenceStore(), //
PreferenceConstants.CUSTOM_CURRENT_LINE_COLOR, theme.getCurrentLineHighlight());
}
}
@Override
protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
super.configureSourceViewerDecorationSupport(support);
if (AttemptToChangeCurrentLineColorAccordingToTheme) {
adjustCurrentLineColor(getTheme());
support.setCursorLinePainterPreferenceKeys(
AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE,
PreferenceConstants.CUSTOM_CURRENT_LINE_COLOR);
}
}
private static IPreferenceStore getStore() {
return Activator.getDefault().getPreferenceStore();
}
@Override
protected void doSetInput(IEditorInput newInput) throws CoreException {
super.doSetInput(newInput);
registerJBehaveProjectListener();
validateAndMark();
}
private void registerJBehaveProjectListener() {
if (projectListener != null)
getJBehaveProject().removeListener(projectListener);
else
projectListener = new JBehaveProjectListener() {
public void stepsUpdated() {
validateAndMark();
invalidateTextPresentation();
}
};
getJBehaveProject().addListener(projectListener);
}
private void invalidateTextPresentation() {
// make sure to invalidate in the Display thread.
getSourceViewer().getTextWidget().getDisplay().asyncExec(new Runnable() {
public void run() {
getSourceViewer().invalidateTextPresentation();
}
});
}
@Override
protected void editorSaved() {
super.editorSaved();
ProjectAwareFastPartitioner partitioner = (ProjectAwareFastPartitioner) getInputDocument()
.getDocumentPartitioner();
if (partitioner != null) {
partitioner.invalidate();
}
validateAndMark();
}
protected void validateAndMark() {
try {
IDocument document = getInputDocument();
if (document == null) {
return;
}
MarkingStoryValidator validator = new MarkingStoryValidator(getJBehaveProject(), getInputFile(), document);
validator.removeExistingMarkers();
validator.validate();
} catch (Exception e) {
Activator.logError("Failed to validate content", e);
}
}
public IDocument getInputDocument() {
return getDocumentProvider().getDocument(getEditorInput());
}
protected IFile getInputFile() {
IFileEditorInput ife = (IFileEditorInput) getEditorInput();
IFile file = ife.getFile();
return file;
}
@Override
protected void initializeKeyBindingScopes() {
setKeyBindingScopes(new String[] { "org.jbehave.eclipse.storyEditorScope" }); //$NON-NLS-1$
}
@Override
protected void createActions() {
super.createActions();
ResourceBundle bundle = EditorMessages.getBundleForConstructedKeys();
formatTable = new FormatTableAction(bundle, "FormatTable.", this);
formatTable.setActionDefinitionId(EditorActionDefinitionIds.FORMAT_TABLE);
setAction("FormatTable", formatTable);
markAsContentDependentAction(EditorActionDefinitionIds.FORMAT_TABLE, true);
showOutline = new ShowOutlineAction(bundle, "ShowOutline.", this);
showOutline.setActionDefinitionId(EditorActionDefinitionIds.SHOW_OUTLINE);
setAction("ShowOutline", showOutline);
markAsContentDependentAction(EditorActionDefinitionIds.SHOW_OUTLINE, true);
quickSearch = new QuickSearchAction(bundle, "QuickSearch.", this);
quickSearch.setActionDefinitionId(EditorActionDefinitionIds.QUICK_SEARCH);
setAction("QuickSearch", quickSearch);
markAsContentDependentAction(EditorActionDefinitionIds.QUICK_SEARCH, true);
jumpToDeclaration = new JumpToDeclarationAction(bundle, "JumpToDeclaration.", this);
jumpToDeclaration.setActionDefinitionId(EditorActionDefinitionIds.JUMP_TO_DECLARATION);
setAction("JumpToDeclaration", jumpToDeclaration);
markAsContentDependentAction(EditorActionDefinitionIds.JUMP_TO_DECLARATION, true);
toggleComment = new ToggleCommentAction(bundle, "ToggleComment.", this);
toggleComment.setActionDefinitionId(EditorActionDefinitionIds.TOGGLE_COMMENT);
setAction("ToggleComment", toggleComment);
markAsContentDependentAction(EditorActionDefinitionIds.JUMP_TO_DECLARATION, true);
}
public void editorContextMenuAboutToShow(MenuManager menu) {
super.editorContextMenuAboutToShow(menu);
addAction(menu, "FormatTable");
addAction(menu, "ToggleComment");
}
protected void editorContextMenuAboutToShow(IMenuManager menu) {
super.editorContextMenuAboutToShow(menu);
System.out.println(">>> Editable "+isEditable());
if (isEditable()) {
addAction(menu, ITextEditorActionConstants.GROUP_EDIT, EditorActionDefinitionIds.FORMAT_TABLE);
addAction(menu, ITextEditorActionConstants.GROUP_EDIT, EditorActionDefinitionIds.TOGGLE_COMMENT);
}
}
public Iterable<StepCandidate> getStepCandidates() {
Visitor<StepCandidate, StepCandidate> collector = new Visitor<StepCandidate, StepCandidate>() {
@Override
public void visit(StepCandidate step) {
add(step);
}
};
try {
getJBehaveProject().traverseSteps(collector);
} catch (JavaModelException e) {
Activator.logError("Failed to collect StepCandidate", e);
}
return collector.getElementsFound();
}
public JBehaveProject getJBehaveProject() {
return JBehaveProjectRegistry.get().getOrCreateProject(getInputFile().getProject());
}
public void insert(StepCandidate pStep) {
Point point = getSourceViewer().getSelectedRange();
try {
getInputDocument().replace(point.x, 0, pStep.fullStep() + "\n");
} catch (BadLocationException e) {
Activator.logError("Failed to insert step candidate", e);
}
}
public void insertAsTemplate(StepCandidate candidate) {
IDocument document = getInputDocument();
Point point = getSourceViewer().getSelectedRange();
int lineNo = getSourceViewer().getTextWidget().getLineAtOffset(point.x);
int lineOffset = getSourceViewer().getTextWidget().getOffsetAtLine(lineNo);
Region replacementRegion = new Region(lineOffset, 0);
TemplateContextType contextType = StoryContextType.getTemplateContextType();
TemplateContext templateContext = new DocumentTemplateContext(contextType, document,
replacementRegion.getOffset(), replacementRegion.getLength());
String templateText = TemplateUtils.templatizeVariables(candidate.fullStep()) + "\n";
Template template = new Template(candidate.stepPattern, candidate.fullStep(), StoryContextType.STORY_CONTEXT_TYPE_ID,
templateText, false);
new TemplateProposal(template, templateContext, replacementRegion, null, 0).apply(getSourceViewer(), (char) 0,
SWT.CONTROL, replacementRegion.getOffset());
}
public void jumpToMethod() {
try {
new StepJumper(getJBehaveProject()).jumpToSelectedMethod(getSourceViewer());
} catch (PartInitException e) {
Activator.logError("Failed to jump to method", e);
} catch (JavaModelException e) {
Activator.logError("Failed to jump to method", e);
}
}
public void applyChange(Effect<ISourceViewer> e) {
ISourceViewer sourceViewer = getSourceViewer();
beginCompoundChange(sourceViewer);
try {
e.e(sourceViewer);
} finally {
endCompoundChange(sourceViewer);
}
}
private static void endCompoundChange(ITextViewer viewer) {
if (viewer instanceof ITextViewerExtension) {
ITextViewerExtension extension = (ITextViewerExtension) viewer;
IRewriteTarget target = extension.getRewriteTarget();
target.endCompoundChange();
}
}
private static void beginCompoundChange(ITextViewer viewer) {
if (viewer instanceof ITextViewerExtension) {
ITextViewerExtension extension = (ITextViewerExtension) viewer;
IRewriteTarget target = extension.getRewriteTarget();
target.beginCompoundChange();
}
}
public IProject getProject() {
return getInputFile().getProject();
}
public void showRange(int offset, int length) {
getSourceViewer().revealRange(offset, length);
getSourceViewer().setRangeIndication(offset, length, true);
}
public List<OutlineModel> getOutlineModels() {
OutlineModelBuilder builder = new OutlineModelBuilder(getLocalizedStepSupport(), getInputDocument());
return builder.build();
}
protected LocalizedStepSupport getLocalizedStepSupport() {
return getJBehaveProject().getLocalizedStepSupport();
}
public void addTextListener(ITextListener textListener) {
getSourceViewer().addTextListener(textListener);
}
public void removeTextListener(ITextListener textListener) {
getSourceViewer().removeTextListener(textListener);
}
@Override
public synchronized Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
if (adapter.equals(IContentOutlinePage.class)) {
if (outlineView == null)
outlineView = new OutlineView(this, Activator.getDefault().getImageRegistry());
return outlineView;
}
return super.getAdapter(adapter);
}
public synchronized void outlinePageClosed() {
outlineView = null;
}
}