<commit_before>/*******************************************************************************
* Copyright (c) 2007 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*    <NAME> (<EMAIL>) - initial API and implementation
*******************************************************************************/

package com.redhat.ceylon.eclipse.code.editor;

import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.CORRECT_INDENTATION;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.FORMAT;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_MATCHING_FENCE;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_NEXT_TARGET;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_PREVIOUS_TARGET;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.RESTORE_PREVIOUS;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.SELECT_ENCLOSING;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.SHOW_OUTLINE;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.TOGGLE_COMMENT;
import static com.redhat.ceylon.eclipse.code.editor.EditorInputUtils.getFile;
import static com.redhat.ceylon.eclipse.code.editor.EditorInputUtils.getPath;
import static com.redhat.ceylon.eclipse.code.editor.SourceArchiveDocumentProvider.isSrcArchive;
import static com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage.TYPE_ANALYSIS;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.PROBLEM_MARKER_ID;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.TASK_MARKER_ID;
import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.PLUGIN_ID;
import static java.util.ResourceBundle.getBundle;
import static org.eclipse.core.resources.IResourceChangeEvent.POST_BUILD;
import static org.eclipse.core.resources.IncrementalProjectBuilder.CLEAN_BUILD;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import static org.eclipse.ui.PlatformUI.getWorkbench;
import static org.eclipse.ui.texteditor.ITextEditorActionConstants.GROUP_RULERS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.DELETE_NEXT_WORD;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.SELECT_WORD_NEXT;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.WORD_NEXT;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.WORD_PREVIOUS;
import static org.eclipse.ui.texteditor.spelling.SpellingService.PREFERENCE_SPELLING_ENABLED;

import java.io.IOException;
import java.lang.reflect.Method;
import java.text.BreakIterator;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.ui.actions.IToggleBreakpointsTarget;
import org.eclipse.debug.ui.actions.ToggleBreakpointAction;
import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.viewsupport.IProblemChangedListener;
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.preference.PreferenceConverter;
import org.eclipse.jface.resource.FontRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelListener;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.IVerticalRulerInfo;
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.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CaretEvent;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ContentAssistAction;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.IEditorStatusLine;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.TextNavigationAction;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

import ceylon.language.StringBuilder;

import com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider;
import com.redhat.ceylon.eclipse.code.outline.CeylonOutlineBuilder;
import com.redhat.ceylon.eclipse.code.outline.CeylonOutlinePage;
import com.redhat.ceylon.eclipse.code.parse.CeylonParseController;
import com.redhat.ceylon.eclipse.code.parse.CeylonParserScheduler;
import com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
import com.redhat.ceylon.eclipse.ui.CeylonResources;

/**
 * An editor for Ceylon source code.
 * 
 * @author <NAME>
 * @author <NAME>
 * @author <NAME>
 */
public class CeylonEditor extends TextEditor {
	
	public static final String MESSAGE_BUNDLE= "com.redhat.ceylon.eclipse.code.editor.EditorActionMessages";

	private static final int REPARSE_SCHEDULE_DELAY= 100;

    /** 
     * Parent annotation ID
     */
    public static final String PARSE_ANNOTATION_TYPE = PLUGIN_ID + 
    		".parseAnnotation";

    /**
     * Annotation ID for a parser annotation w/ severity = error.
     * Must match the ID of the corresponding annotationTypes 
     * extension in the plugin.xml.
     */
    public static final String PARSE_ANNOTATION_TYPE_ERROR= PARSE_ANNOTATION_TYPE + 
    		".error";

    /**
     * Annotation ID for a parser annotation w/ severity = warning. 
     * Must match the ID of the corresponding annotationTypes 
     * extension in the plugin.xml.
     */
    public static final String PARSE_ANNOTATION_TYPE_WARNING= PARSE_ANNOTATION_TYPE + 
    		".warning";

    /**
     * Annotation ID for a parser annotation w/ severity = info. 
     * Must match the ID of the corresponding annotationTypes 
     * extension in the plugin.xml.
     */
    public static final String PARSE_ANNOTATION_TYPE_INFO= PARSE_ANNOTATION_TYPE + 
    		".info";

    /** Preference key for matching brackets */
    protected final static String MATCHING_BRACKETS= "matchingBrackets";

    /** Preference key for matching brackets color */
    protected final static String MATCHING_BRACKETS_COLOR= "matchingBracketsColor";

    private CeylonParserScheduler parserScheduler;
    private ProblemMarkerManager problemMarkerManager;
    private ICharacterPairMatcher bracketMatcher;
    //private SubActionBars fActionBars;
    //private DefaultPartListener fRefreshContributions;
    private ToggleBreakpointAction toggleBreakpointAction;
    private IAction enableDisableBreakpointAction;
    private IResourceChangeListener buildListener;
    private IResourceChangeListener moveListener;
    private IDocumentListener documentListener;
    private FoldingActionGroup foldingActionGroup;
    private SourceArchiveDocumentProvider sourceArchiveDocumentProvider;
    private ToggleBreakpointAdapter toggleBreakpointTarget;
    private CeylonOutlinePage outlinePage;
    private boolean backgroundParsingPaused;
    private CeylonParseController parseController;
        
    //public static ResourceBundle fgBundleForConstructedKeys= getBundle(MESSAGE_BUNDLE);
    
    //public static final String IMP_CODING_ACTION_SET = RuntimePlugin.IMP_RUNTIME + ".codingActionSet";
    //public static final String IMP_OPEN_ACTION_SET = RuntimePlugin.IMP_RUNTIME + ".openActionSet";

    public CeylonEditor() {
        // SMS 4 Apr 2007
        // Do not set preference store with store obtained from plugin; one is
        // already defined for the parent text editor and populated with relevant
        // preferences
        // setPreferenceStore(CeylonPlugin.getInstance().getPreferenceStore());
        setSourceViewerConfiguration(createSourceViewerConfiguration());
        setRangeIndicator(new CeylonRangeIndicator());
        configureInsertMode(SMART_INSERT, true);
        setInsertMode(SMART_INSERT);
        problemMarkerManager= new ProblemMarkerManager();
	}

	static String[][] getFences() {
		return new String[][] { { "(", ")" }, { "[", "]" }, { "{", "}" } };
	}
	
	public synchronized void pauseBackgroundParsing() {
		backgroundParsingPaused = true;
	}
    
	public synchronized void unpauseBackgroundParsing() {
		backgroundParsingPaused = false;
	}
	
	public synchronized boolean isBackgroundParsingPaused() {
		return backgroundParsingPaused;
	}
	
    /**
     * Sub-classes may override this method to extend the behavior provided by IMP's
     * standard StructuredSourceViewerConfiguration.
     * @return the StructuredSourceViewerConfiguration to use with this editor
     */
    protected CeylonSourceViewerConfiguration createSourceViewerConfiguration() {
    	return new CeylonSourceViewerConfiguration(getPreferenceStore(), this);
    }

    public IPreferenceStore getPrefStore() {
    	return super.getPreferenceStore();
    }

    @SuppressWarnings("rawtypes")
    public Object getAdapter(Class required) {
        if (IContentOutlinePage.class.equals(required)) {
            return getOutlinePage();
        }
        if (IToggleBreakpointsTarget.class.equals(required)) {
            return getToggleBreakpointAdapter();
        }
        /*if (IContextProvider.class.equals(required)) {
            return IMPHelp.getHelpContextProvider(this, fLanguageServiceManager, IMP_EDITOR_CONTEXT);
        }*/
        return super.getAdapter(required);
    }

	public Object getToggleBreakpointAdapter() {
		if (toggleBreakpointTarget == null) {
		    toggleBreakpointTarget = new ToggleBreakpointAdapter();
		}
		return toggleBreakpointTarget;
	}

	public Object getOutlinePage() {
		if (outlinePage == null) {
		    outlinePage = new CeylonOutlinePage(getParseController(),
		            new CeylonOutlineBuilder());
		    parserScheduler.addModelListener(outlinePage);
		    getSourceViewer().getTextWidget().addCaretListener(outlinePage);
		    //myOutlinePage.update(parseController);
		 }
		 return outlinePage;
	}

    protected void createActions() {
        super.createActions();

        final ResourceBundle bundle= getBundle(MESSAGE_BUNDLE);
        
        Action action= new ContentAssistAction(bundle, "ContentAssistProposal.", this);
        action.setActionDefinitionId(CONTENT_ASSIST_PROPOSALS);
        setAction("ContentAssistProposal", action);
        markAsStateDependentAction("ContentAssistProposal", true);

        toggleBreakpointAction= new ToggleBreakpointAction(this, 
        		getDocumentProvider().getDocument(getEditorInput()), 
        		getVerticalRuler());
        setAction("ToggleBreakpoint", action);
        
        enableDisableBreakpointAction= new RulerEnableDisableBreakpointAction(this, 
        		getVerticalRuler());
        setAction("ToggleBreakpoint", action);

        action= new TextOperationAction(bundle, "Format.", this, 
        		CeylonSourceViewer.FORMAT);
        action.setActionDefinitionId(FORMAT);
        setAction("Format", action);
        markAsStateDependentAction("Format", true);
        markAsSelectionDependentAction("Format", true);
        getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.FORMAT_ACTION);

        action= new TextOperationAction(bundle, "ShowOutline.", this, 
        		CeylonSourceViewer.SHOW_OUTLINE, true /* runsOnReadOnly */);
        action.setActionDefinitionId(SHOW_OUTLINE);
        setAction(SHOW_OUTLINE, action);
        getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.SHOW_OUTLINE_ACTION);

        action= new TextOperationAction(bundle, "ToggleComment.", this, 
        		CeylonSourceViewer.TOGGLE_COMMENT);
        action.setActionDefinitionId(TOGGLE_COMMENT);
        setAction(TOGGLE_COMMENT, action);
        getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.TOGGLE_COMMENT_ACTION);

        action= new TextOperationAction(bundle, "CorrectIndentation.", this, 
        		CeylonSourceViewer.CORRECT_INDENTATION);
        action.setActionDefinitionId(CORRECT_INDENTATION);
        setAction(CORRECT_INDENTATION, action);

        action= new GotoMatchingFenceAction(this);
        action.setActionDefinitionId(GOTO_MATCHING_FENCE);
        setAction(GOTO_MATCHING_FENCE, action);

        action= new GotoPreviousTargetAction(this);
        action.setActionDefinitionId(GOTO_PREVIOUS_TARGET);
        setAction(GOTO_PREVIOUS_TARGET, action);

        action= new GotoNextTargetAction(this);
        action.setActionDefinitionId(GOTO_NEXT_TARGET);
        setAction(GOTO_NEXT_TARGET, action);

        action= new SelectEnclosingAction(this);
        action.setActionDefinitionId(SELECT_ENCLOSING);
        setAction(SELECT_ENCLOSING, action);

        action= new RestorePreviousSelectionAction(this);
        action.setActionDefinitionId(RESTORE_PREVIOUS);
        setAction(RESTORE_PREVIOUS, action);

    	action= new TextOperationAction(bundle, "ShowHierarchy.", this, 
    			CeylonSourceViewer.SHOW_HIERARCHY, true);
        action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_HIERARCHY);
        setAction(EditorActionIds.SHOW_CEYLON_HIERARCHY, action);

    	action= new TextOperationAction(bundle, "ShowCode.", this, 
    			CeylonSourceViewer.SHOW_CODE, true);
        action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_CODE);
        setAction(EditorActionIds.SHOW_CEYLON_CODE, action);
        
        action= new TerminateStatementAction(this);
        action.setActionDefinitionId(EditorActionIds.TERMINATE_STATEMENT);
        setAction(EditorActionIds.TERMINATE_STATEMENT, action);

        foldingActionGroup= new FoldingActionGroup(this, this.getSourceViewer());
        
        getAction(ITextEditorActionConstants.SHIFT_LEFT)
            .setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
            		.getDescriptor(CeylonResources.SHIFT_LEFT));
        getAction(ITextEditorActionConstants.SHIFT_RIGHT)
            .setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
        		.getDescriptor(CeylonResources.SHIFT_RIGHT));
        
        IAction qaa=getAction(ITextEditorActionConstants.QUICK_ASSIST);
        qaa.setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
    		    .getDescriptor(CeylonResources.QUICK_ASSIST));
        qaa.setText("Quick Fix/Assist");
        
    }
    
    @Override
    protected void createNavigationActions() {
        super.createNavigationActions();
        
        final StyledText textWidget= getSourceViewer().getTextWidget();

        /*IAction action= new SmartLineStartAction(textWidget, false);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_START);
        editor.setAction(ITextEditorActionDefinitionIds.LINE_START, action);

        action= new SmartLineStartAction(textWidget, true);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_LINE_START);
        editor.setAction(ITextEditorActionDefinitionIds.SELECT_LINE_START, action);*/

        setAction(WORD_PREVIOUS, new NavigatePreviousSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL);

        setAction(WORD_NEXT, new NavigateNextSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL);

        setAction(SELECT_WORD_PREVIOUS, new SelectPreviousSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL);

        setAction(SELECT_WORD_NEXT, new SelectNextSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL);

        setAction(DELETE_PREVIOUS_WORD, new DeletePreviousSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.BS, SWT.NULL);
        markAsStateDependentAction(DELETE_PREVIOUS_WORD, true);

        setAction(DELETE_NEXT_WORD, new DeleteNextSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.DEL, SWT.NULL);
        markAsStateDependentAction(DELETE_NEXT_WORD, true);
    }

    protected class NavigateNextSubWordAction 
    extends NextSubWordAction {
    	public NavigateNextSubWordAction() {
    		super(ST.WORD_NEXT);
    		setActionDefinitionId(WORD_NEXT);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
    	}
    }

    protected class NavigatePreviousSubWordAction 
    extends PreviousSubWordAction {
    	public NavigatePreviousSubWordAction() {
    		super(ST.WORD_PREVIOUS);
    		setActionDefinitionId(WORD_PREVIOUS);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
    	}
    }

    protected abstract class NextSubWordAction 
    extends TextNavigationAction {

    	protected CeylonWordIterator fIterator= new CeylonWordIterator();

    	/**
    	 * Creates a new next sub-word action.
    	 *
    	 * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST.
    	 */
    	protected NextSubWordAction(int code) {
    		super(getSourceViewer().getTextWidget(), code);
    	}

    	@Override
    	public void run() {
    		// Check whether we are in a java code partition and the preference is enabled

    		final ISourceViewer viewer= getSourceViewer();
    		final IDocument document= viewer.getDocument();
    		try {
    			fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
    			int position= widgetOffset2ModelOffset(viewer, 
    					viewer.getTextWidget().getCaretOffset());
    			if (position == -1)
    				return;

    			int next= findNextPosition(position);
    			if (next != BreakIterator.DONE) {
    				setCaretPosition(next);
    				getTextWidget().showSelection();
    				fireSelectionChanged();
    			}
    		} 
    		catch (BadLocationException x) {
    			// ignore
    		}
    	}

    	/**
    	 * Finds the next position after the given position.
    	 *
    	 * @param position the current position
    	 * @return the next position
    	 */
    	protected int findNextPosition(int position) {
    		ISourceViewer viewer= getSourceViewer();
    		int widget= -1;
    		int next= position;
    		while (next != BreakIterator.DONE && widget == -1) { // XXX: optimize
    			next= fIterator.following(next);
    			if (next != BreakIterator.DONE)
    				widget= modelOffset2WidgetOffset(viewer, next);
    		}

    		IDocument document= viewer.getDocument();
    		LinkedModeModel model= LinkedModeModel.getModel(document, position);
    		if (model != null) {
    			LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
    			if (linkedPosition != null) {
    				int linkedPositionEnd= linkedPosition.getOffset() + linkedPosition.getLength();
    				if (position != linkedPositionEnd && linkedPositionEnd < next)
    					next= linkedPositionEnd;
    			} else {
    				LinkedPosition nextLinkedPosition= model.findPosition(new LinkedPosition(document, next, 0));
    				if (nextLinkedPosition != null) {
    					int nextLinkedPositionOffset= nextLinkedPosition.getOffset();
    					if (position != nextLinkedPositionOffset && nextLinkedPositionOffset < next)
    						next= nextLinkedPositionOffset;
    				}
    			}
    		}

    		return next;
    	}

    	/**
    	 * Sets the caret position to the sub-word boundary given with 
    	 * <code>position</code>.
    	 *
    	 * @param position Position where the action should move the caret
    	 */
    	protected abstract void setCaretPosition(int position);
    }

    protected abstract class PreviousSubWordAction 
    extends TextNavigationAction {

    	protected CeylonWordIterator fIterator= new CeylonWordIterator();

    	/**
    	 * Creates a new previous sub-word action.
    	 *
    	 * @param code Action code for the default operation. Must be an 
    	 * action code from @see org.eclipse.swt.custom.ST.
    	 */
    	protected PreviousSubWordAction(final int code) {
    		super(getSourceViewer().getTextWidget(), code);
    	}

    	@Override
    	public void run() {
    		// Check whether we are in a java code partition and the preference is enabled

    		final ISourceViewer viewer= getSourceViewer();
    		final IDocument document= viewer.getDocument();
    		try {
    			fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
    			int position= widgetOffset2ModelOffset(viewer, 
    					viewer.getTextWidget().getCaretOffset());
    			if (position == -1)
    				return;

    			int previous= findPreviousPosition(position);
    			if (previous != BreakIterator.DONE) {
    				setCaretPosition(previous);
    				getTextWidget().showSelection();
    				fireSelectionChanged();
    			}
    		} catch (BadLocationException x) {
    			// ignore - getLineOfOffset failed
    		}

    	}

    	/**
    	 * Finds the previous position before the given position.
    	 *
    	 * @param position the current position
    	 * @return the previous position
    	 */
    	protected int findPreviousPosition(int position) {
    		ISourceViewer viewer= getSourceViewer();
    		int widget= -1;
    		int previous= position;
    		while (previous != BreakIterator.DONE && widget == -1) { // XXX: optimize
    			previous= fIterator.preceding(previous);
    			if (previous != BreakIterator.DONE)
    				widget= modelOffset2WidgetOffset(viewer, previous);
    		}

    		IDocument document= viewer.getDocument();
    		LinkedModeModel model= LinkedModeModel.getModel(document, position);
    		if (model != null) {
    			LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
    			if (linkedPosition != null) {
    				int linkedPositionOffset= linkedPosition.getOffset();
    				if (position != linkedPositionOffset && previous < linkedPositionOffset)
    					previous= linkedPositionOffset;
    			} else {
    				LinkedPosition previousLinkedPosition= model.findPosition(new LinkedPosition(document, previous, 0));
    				if (previousLinkedPosition != null) {
    					int previousLinkedPositionEnd= previousLinkedPosition.getOffset() + previousLinkedPosition.getLength();
    					if (position != previousLinkedPositionEnd && previous < previousLinkedPositionEnd)
    						previous= previousLinkedPositionEnd;
    				}
    			}
    		}

    		return previous;
    	}

    	/**
    	 * Sets the caret position to the sub-word boundary given with <code>position</code>.
    	 *
    	 * @param position Position where the action should move the caret
    	 */
    	protected abstract void setCaretPosition(int position);
    }

    protected class SelectNextSubWordAction 
    extends NextSubWordAction {
    	public SelectNextSubWordAction() {
    		super(ST.SELECT_WORD_NEXT);
    		setActionDefinitionId(SELECT_WORD_NEXT);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		final ISourceViewer viewer= getSourceViewer();
    
    		final StyledText text= viewer.getTextWidget();
    		if (text != null && !text.isDisposed()) {
    
    			final Point selection= text.getSelection();
    			final int caret= text.getCaretOffset();
    			final int offset= modelOffset2WidgetOffset(viewer, position);
    
    			if (caret == selection.x)
    				text.setSelectionRange(selection.y, offset - selection.y);
    			else
    				text.setSelectionRange(selection.x, offset - selection.x);
    		}
    	}
    }
    
    protected class SelectPreviousSubWordAction 
    extends PreviousSubWordAction {
    	public SelectPreviousSubWordAction() {
    		super(ST.SELECT_WORD_PREVIOUS);
    		setActionDefinitionId(SELECT_WORD_PREVIOUS);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		final ISourceViewer viewer= getSourceViewer();
    
    		final StyledText text= viewer.getTextWidget();
    		if (text != null && !text.isDisposed()) {
    
    			final Point selection= text.getSelection();
    			final int caret= text.getCaretOffset();
    			final int offset= modelOffset2WidgetOffset(viewer, position);
    
    			if (caret == selection.x)
    				text.setSelectionRange(selection.y, offset - selection.y);
    			else
    				text.setSelectionRange(selection.x, offset - selection.x);
    		}
    	}
    }
    
    protected class DeleteNextSubWordAction 
    extends NextSubWordAction implements IUpdate {
    	public DeleteNextSubWordAction() {
    		super(ST.DELETE_WORD_NEXT);
    		setActionDefinitionId(DELETE_NEXT_WORD);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		if (!validateEditorInputState())
    			return;
    
    		final ISourceViewer viewer= getSourceViewer();
    		StyledText text= viewer.getTextWidget();
    		Point selection= viewer.getSelectedRange();
    		final int caret, length;
    		if (selection.y != 0) {
    			caret= selection.x;
    			length= selection.y;
    		} else {
    			caret= widgetOffset2ModelOffset(viewer, text.getCaretOffset());
    			length= position - caret;
    		}
    
    		try {
    			viewer.getDocument().replace(caret, length, "");
    		} catch (BadLocationException exception) {
    			// Should not happen
    		}
    	}
    	public void update() {
    		setEnabled(isEditorInputModifiable());
    	}
    }
    
    protected class DeletePreviousSubWordAction 
    extends PreviousSubWordAction implements IUpdate {
    	public DeletePreviousSubWordAction() {
    		super(ST.DELETE_WORD_PREVIOUS);
    		setActionDefinitionId(DELETE_PREVIOUS_WORD);
    	}
    	@Override
    	protected void setCaretPosition(int position) {
    		if (!validateEditorInputState())
    			return;
    
    		final int length;
    		final ISourceViewer viewer= getSourceViewer();
    		StyledText text= viewer.getTextWidget();
    		Point selection= viewer.getSelectedRange();
    		if (selection.y != 0) {
    			position= selection.x;
    			length= selection.y;
    		} else {
    			length= widgetOffset2ModelOffset(viewer, text.getCaretOffset()) - position;
    		}
    
    		try {
    			viewer.getDocument().replace(position, length, "");
    		} catch (BadLocationException exception) {
    			// Should not happen
    		}
    	}
    	public void update() {
    		setEnabled(isEditorInputModifiable());
    	}
    }

    protected void initializeKeyBindingScopes() {
        setKeyBindingScopes(new String[] { PLUGIN_ID + ".context" });
    }

    //private QuickMenuAction fQuickAccessAction;
    //private IHandlerActivation fQuickAccessHandlerActivation;
    //private IHandlerService fHandlerService;

    //private static final String QUICK_MENU_ID= "org.eclipse.imp.runtime.editor.refactor.quickMenu";

    /*private class RefactorQuickAccessAction extends QuickMenuAction {
        public RefactorQuickAccessAction() {
            super(QUICK_MENU_ID);
        }
        protected void fillMenu(IMenuManager menu) {
            //contributeRefactoringActions(menu);
        }
    }*/

    /*private void installQuickAccessAction() {
        fHandlerService= (IHandlerService) getSite().getService(IHandlerService.class);
        if (fHandlerService != null) {
            fQuickAccessAction= new RefactorQuickAccessAction();
            fQuickAccessHandlerActivation= fHandlerService.activateHandler(fQuickAccessAction.getActionDefinitionId(), 
            		new ActionHandler(fQuickAccessAction));
        }
    }*/


    protected boolean isOverviewRulerVisible() {
        return true;
    }

    protected void rulerContextMenuAboutToShow(IMenuManager menu) {
        addDebugActions(menu);

        super.rulerContextMenuAboutToShow(menu);

        //IMenuManager foldingMenu= new MenuManager("Folding", "projection");

        //menu.appendToGroup(GROUP_RULERS, foldingMenu);

        menu.appendToGroup(GROUP_RULERS, new Separator());
        
        IAction action;
//      action= getAction("FoldingToggle");
//      foldingMenu.add(action);
        action= getAction("FoldingExpandAll");
        menu.appendToGroup(GROUP_RULERS, action);
        //foldingMenu.add(action);
        action= getAction("FoldingCollapseAll");
        //foldingMenu.add(action);
        menu.appendToGroup(GROUP_RULERS, action);
        /*action= getAction("FoldingRestore");
        foldingMenu.add(action);
        action= getAction("FoldingCollapseMembers");
        foldingMenu.add(action);
        action= getAction("FoldingCollapseComments");
        foldingMenu.add(action);*/
    }

    private void addDebugActions(IMenuManager menu) {
        menu.add(toggleBreakpointAction);
        menu.add(enableDisableBreakpointAction);
    }

    /**
     * Sets the given message as error message to this editor's status line.
     *
     * @param msg message to be set
     */
    protected void setStatusLineErrorMessage(String msg) {
        IEditorStatusLine statusLine= (IEditorStatusLine) getAdapter(IEditorStatusLine.class);
        if (statusLine != null)
            statusLine.setMessage(true, msg, null);
    }

    /**
     * Sets the given message as message to this editor's status line.
     *
     * @param msg message to be set
     * @since 3.0
     */
    protected void setStatusLineMessage(String msg) {
        IEditorStatusLine statusLine= (IEditorStatusLine) getAdapter(IEditorStatusLine.class);
        if (statusLine != null)
            statusLine.setMessage(false, msg, null);
    }

    public ProblemMarkerManager getProblemMarkerManager() {
        return problemMarkerManager;
    }
    
    @Override
    protected void setTitleImage(Image titleImage) {
    	super.setTitleImage(titleImage);
    }

    public IDocumentProvider getDocumentProvider() {
    	if (isSrcArchive(getEditorInput())) {
    		//Note: I would prefer to register the
    		//document provider in plugin.xml but
    		//I don't know how to uniquely identity
    		//that a IURIEditorInput is a source
    		//archive there
    		if (sourceArchiveDocumentProvider==null) {
    			sourceArchiveDocumentProvider = new SourceArchiveDocumentProvider();
    		}
    		return sourceArchiveDocumentProvider;
    	}
    	else {
    		return super.getDocumentProvider();
    	}
    }
    
    public CeylonSourceViewer getCeylonSourceViewer() {
    	return (CeylonSourceViewer) super.getSourceViewer();
    }

    public void createPartControl(Composite parent) {
        
    	// Initialize the parse controller first, since the 
    	// initialization of other things (like the context 
    	// help support) might depend on it.
        initializeParseController();

        // Not sure why the "run the spell checker" pref would 
        // get set, but it does seem to, which gives lots of 
        // annoying squigglies all over the place...
        getPreferenceStore().setValue(PREFERENCE_SPELLING_ENABLED, false);

        super.createPartControl(parent);

        initiateServiceControllers();

        setTitleImageFromLanguageIcon();
        setSourceFontFromPreference();
        setupBracketCloser();
        
        /*((IContextService) getSite().getService(IContextService.class))
                .activateContext(PLUGIN_ID + ".context");*/
        
        //CeylonPlugin.getInstance().getPreferenceStore().addPropertyChangeListener(colorChangeListener);
        currentTheme.getColorRegistry().addListener(colorChangeListener);
        updateFontAndCaret();
        currentTheme.getFontRegistry().addListener(fontChangeListener);
    }

    private void watchForSourceBuild() {        
        getWorkspace().addResourceChangeListener(buildListener= new IResourceChangeListener() {
            public void resourceChanged(IResourceChangeEvent event) {
                if (event.getType()==POST_BUILD && event.getBuildKind()!=CLEAN_BUILD) {
                	scheduleParsing();
                }
            }
        }, IResourceChangeEvent.POST_BUILD);
    }

    public synchronized void scheduleParsing() {
    	if (parserScheduler!=null && !backgroundParsingPaused) {
    		parserScheduler.cancel();
    		parserScheduler.schedule(REPARSE_SCHEDULE_DELAY);
    	}
    }

    private void initializeParseController() {
        IEditorInput editorInput= getEditorInput();
        IFile file = getFile(editorInput);
        IPath filePath = getPath(editorInput);
        parseController = new CeylonParseController();
        IProject project= file!=null && file.exists() ? file.getProject() : null;
        parseController.initialize(filePath, project, annotationCreator);
    }

    private void watchDocument() {
        getSourceViewer().getDocument()
                .addDocumentListener(documentListener= new IDocumentListener() {
            public void documentAboutToBeChanged(DocumentEvent event) {}
            public void documentChanged(DocumentEvent event) {
            	synchronized (CeylonEditor.this) {
            		if (parserScheduler!=null && !backgroundParsingPaused) {
            			parserScheduler.cancel();
            			parserScheduler.schedule(REPARSE_SCHEDULE_DELAY);
            		}
				}
            }
        });
    }

    /*private class BracketInserter implements VerifyKeyListener {
        private final Map<String,String> fFencePairs= new HashMap<String, String>();
        private final String fOpenFences;
        private final Map<Character,Boolean> fCloseFenceMap= new HashMap<Character, Boolean>();
//      private final String CATEGORY= toString();
//      private IPositionUpdater fUpdater= new ExclusivePositionUpdater(CATEGORY);

        public BracketInserter() {
            String[][] pairs= fLanguageServiceManager.getParseController().getSyntaxProperties().getFences();
            StringBuilder sb= new StringBuilder();
            for(int i= 0; i < pairs.length; i++) {
                sb.append(pairs[i][0]);
                fFencePairs.put(pairs[i][0], pairs[i][1]);
            }
            fOpenFences= sb.toString();
        }

        public void setCloseFenceEnabled(char openingFence, boolean enabled) {
            fCloseFenceMap.put(openingFence, enabled);
        }

        public void setCloseFencesEnabled(boolean enabled) {
            for(int i= 0; i < fOpenFences.length(); i++) {
                fCloseFenceMap.put(fOpenFences.charAt(i), enabled);
            }
        }

        public void verifyKey(VerifyEvent event) {
            // early pruning to slow down normal typing as little as possible
            if (!event.doit || getInsertMode() != SMART_INSERT)
                return;

            if (fOpenFences.indexOf(event.character) < 0) {
                return;
            }

            final ISourceViewer sourceViewer= getSourceViewer();
            IDocument document= sourceViewer.getDocument();

            final Point selection= sourceViewer.getSelectedRange();
            final int offset= selection.x;
            final int length= selection.y;

            try {
//              IRegion startLine= document.getLineInformationOfOffset(offset);
//              IRegion endLine= document.getLineInformationOfOffset(offset + length);

                // TODO Ask the parser/scanner whether the close fence is valid here
                // (i.e. whether it would recover by inserting the matching close fence character)
                // Right now, naively insert the closing fence regardless.

                ITypedRegion partition= TextUtilities.getPartition(document, getSourceViewerConfiguration().getConfiguredDocumentPartitioning(sourceViewer), offset, true);
                if (!IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType()))
                    return;

                if (!validateEditorInputState())
                    return;

                final String inputStr= new String(new char[] { event.character });
                final String closingFence= fFencePairs.get(inputStr);
                final StringBuffer buffer= new StringBuffer();
                buffer.append(inputStr);
                buffer.append(closingFence);

                document.replace(offset, length, buffer.toString());
                sourceViewer.setSelectedRange(offset + inputStr.length(), 0);

                event.doit= false;
            } 
            catch (BadLocationException e) {
                e.printStackTrace();
            }
        }
    }*/

    //private BracketInserter fBracketInserter;
    //private final String CLOSE_FENCES= PreferenceConstants.EDITOR_CLOSE_FENCES;

    private void setupBracketCloser() {
        // Bug #536: Disable for now, until we can be more intelligent 
        //about when to overwrite an existing (subsequent) close-fence char.
        /*IParseController parseController = fLanguageServiceManager.getParseController();
        if (parseController == null || parseController.getSyntaxProperties() == null || 
        		parseController.getSyntaxProperties().getFences() == null) {
            return;
        }

        //Preference key for automatically closing brackets and parenthesis 
        boolean closeFences= fLangSpecificPrefs.getBooleanPreference(CLOSE_FENCES); // false if no lang-specific setting

        fBracketInserter= new BracketInserter();
        fBracketInserter.setCloseFencesEnabled(closeFences);

        ISourceViewer sourceViewer= getSourceViewer();
        if (sourceViewer instanceof ITextViewerExtension)
            ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(fBracketInserter);*/
    }

    /**
     * The following listener is intended to detect when the document associated
     * with this editor changes its identity, which happens when, e.g., the
     * underlying resource gets moved or renamed.
     */
    private IPropertyListener fEditorInputPropertyListener = new IPropertyListener() {
        public void propertyChanged(Object source, int propId) {
            if (source == CeylonEditor.this && propId == IEditorPart.PROP_INPUT) {
                IDocument oldDoc= getParseController().getDocument();
                IDocument curDoc= getDocumentProvider().getDocument(getEditorInput()); 
                if (curDoc!=oldDoc) {
                    // Need to unwatch the old document and watch the new document
                    oldDoc.removeDocumentListener(documentListener);
                    curDoc.addDocumentListener(documentListener);
                }
            }
        }
    };

    private void watchForSourceMove() {
        // We need to see when the editor input changes, so we can watch the new document
        addPropertyListener(fEditorInputPropertyListener);
        getWorkspace().addResourceChangeListener(moveListener= new IResourceChangeListener() {
        	public void resourceChanged(IResourceChangeEvent event) {
        		if (event.getType()==IResourceChangeEvent.POST_CHANGE) {
        			IProject project = parseController.getProject();
        			if (project!=null) { //things extrenal to the workspace don't move
        				IPath oldWSRelPath= project.getFullPath().append(parseController.getPath());
        				IResourceDelta rd= event.getDelta().findMember(oldWSRelPath);
        				if (rd != null) {
        					if ((rd.getFlags() & IResourceDelta.MOVED_TO) == IResourceDelta.MOVED_TO) {
        						// The net effect of the following is to re-initialize() the IParseController with the new path
        						IPath newPath= rd.getMovedToPath();
        						IPath newProjRelPath= newPath.removeFirstSegments(1);
        						String newProjName= newPath.segment(0);
        						IProject proj= project.getName().equals(newProjName) ? 
        								project : project.getWorkspace().getRoot()
        							            .getProject(newProjName);
        						// Tell the IParseController about the move - it caches the path
        						// fParserScheduler.cancel(); // avoid a race condition if ParserScheduler was starting/in the middle of a run
        						parseController.initialize(newProjRelPath, proj, annotationCreator);
        					}
        				}
        			}
        		}
            }
        });
    }

    private void setSourceFontFromPreference() {
        String fontName = WorkbenchPlugin.getDefault().getPreferenceStore()
        		.getString(JFaceResources.TEXT_FONT);
        FontRegistry fontRegistry= CeylonPlugin.getInstance().getFontRegistry();
        if (!fontRegistry.hasValueFor(fontName)) {
            fontRegistry.put(fontName, PreferenceConverter.readFontData(fontName));
        }
        Font sourceFont= fontRegistry.get(fontName);
        if (sourceFont!=null) {
            getSourceViewer().getTextWidget().setFont(sourceFont);
        }
    }

    private void initiateServiceControllers() {
        try {
            final CeylonSourceViewer sourceViewer = (CeylonSourceViewer) getSourceViewer();
                        
            annotationUpdater= new IProblemChangedListener() {
                public void problemsChanged(IResource[] changedResources, 
                		boolean isMarkerChange) {
                    // Remove annotations that were resolved by changes to 
                	// other resources. 
                	// TODO: It would be better to match the markers to the 
                	// annotations, and decide which annotations to remove.
                    scheduleParsing();
                }
            };
            problemMarkerManager.addListener(annotationUpdater);
            
            editorIconUpdater= new EditorIconUpdater(this);
            problemMarkerManager.addListener(editorIconUpdater);

			parserScheduler= new CeylonParserScheduler(parseController, this, <commit_msg>disable apparently unnecessary code<commit_after>/*******************************************************************************
* Copyright (c) 2007 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*    <NAME> (<EMAIL>) - initial API and implementation
*******************************************************************************/

package com.redhat.ceylon.eclipse.code.editor;

import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.CORRECT_INDENTATION;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.FORMAT;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_MATCHING_FENCE;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_NEXT_TARGET;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_PREVIOUS_TARGET;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.RESTORE_PREVIOUS;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.SELECT_ENCLOSING;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.SHOW_OUTLINE;
import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.TOGGLE_COMMENT;
import static com.redhat.ceylon.eclipse.code.editor.EditorInputUtils.getFile;
import static com.redhat.ceylon.eclipse.code.editor.EditorInputUtils.getPath;
import static com.redhat.ceylon.eclipse.code.editor.SourceArchiveDocumentProvider.isSrcArchive;
import static com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage.TYPE_ANALYSIS;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.PROBLEM_MARKER_ID;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.TASK_MARKER_ID;
import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.PLUGIN_ID;
import static java.util.ResourceBundle.getBundle;
import static org.eclipse.core.resources.IResourceChangeEvent.POST_BUILD;
import static org.eclipse.core.resources.IncrementalProjectBuilder.CLEAN_BUILD;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import static org.eclipse.ui.PlatformUI.getWorkbench;
import static org.eclipse.ui.texteditor.ITextEditorActionConstants.GROUP_RULERS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.DELETE_NEXT_WORD;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.SELECT_WORD_NEXT;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.WORD_NEXT;
import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.WORD_PREVIOUS;
import static org.eclipse.ui.texteditor.spelling.SpellingService.PREFERENCE_SPELLING_ENABLED;

import java.io.IOException;
import java.lang.reflect.Method;
import java.text.BreakIterator;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.ui.actions.IToggleBreakpointsTarget;
import org.eclipse.debug.ui.actions.ToggleBreakpointAction;
import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.viewsupport.IProblemChangedListener;
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.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelListener;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.IVerticalRulerInfo;
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.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CaretEvent;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ContentAssistAction;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.IEditorStatusLine;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.IUpdate;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.TextNavigationAction;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

import ceylon.language.StringBuilder;

import com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider;
import com.redhat.ceylon.eclipse.code.outline.CeylonOutlineBuilder;
import com.redhat.ceylon.eclipse.code.outline.CeylonOutlinePage;
import com.redhat.ceylon.eclipse.code.parse.CeylonParseController;
import com.redhat.ceylon.eclipse.code.parse.CeylonParserScheduler;
import com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
import com.redhat.ceylon.eclipse.ui.CeylonResources;

/**
 * An editor for Ceylon source code.
 * 
 * @author <NAME>
 * @author <NAME>
 * @author <NAME>
 */
public class CeylonEditor extends TextEditor {
	
	public static final String MESSAGE_BUNDLE= "com.redhat.ceylon.eclipse.code.editor.EditorActionMessages";

	private static final int REPARSE_SCHEDULE_DELAY= 100;

    /** 
     * Parent annotation ID
     */
    public static final String PARSE_ANNOTATION_TYPE = PLUGIN_ID + 
    		".parseAnnotation";

    /**
     * Annotation ID for a parser annotation w/ severity = error.
     * Must match the ID of the corresponding annotationTypes 
     * extension in the plugin.xml.
     */
    public static final String PARSE_ANNOTATION_TYPE_ERROR= PARSE_ANNOTATION_TYPE + 
    		".error";

    /**
     * Annotation ID for a parser annotation w/ severity = warning. 
     * Must match the ID of the corresponding annotationTypes 
     * extension in the plugin.xml.
     */
    public static final String PARSE_ANNOTATION_TYPE_WARNING= PARSE_ANNOTATION_TYPE + 
    		".warning";

    /**
     * Annotation ID for a parser annotation w/ severity = info. 
     * Must match the ID of the corresponding annotationTypes 
     * extension in the plugin.xml.
     */
    public static final String PARSE_ANNOTATION_TYPE_INFO= PARSE_ANNOTATION_TYPE + 
    		".info";

    /** Preference key for matching brackets */
    protected final static String MATCHING_BRACKETS= "matchingBrackets";

    /** Preference key for matching brackets color */
    protected final static String MATCHING_BRACKETS_COLOR= "matchingBracketsColor";

    private CeylonParserScheduler parserScheduler;
    private ProblemMarkerManager problemMarkerManager;
    private ICharacterPairMatcher bracketMatcher;
    //private SubActionBars fActionBars;
    //private DefaultPartListener fRefreshContributions;
    private ToggleBreakpointAction toggleBreakpointAction;
    private IAction enableDisableBreakpointAction;
    private IResourceChangeListener buildListener;
    private IResourceChangeListener moveListener;
    private IDocumentListener documentListener;
    private FoldingActionGroup foldingActionGroup;
    private SourceArchiveDocumentProvider sourceArchiveDocumentProvider;
    private ToggleBreakpointAdapter toggleBreakpointTarget;
    private CeylonOutlinePage outlinePage;
    private boolean backgroundParsingPaused;
    private CeylonParseController parseController;
        
    //public static ResourceBundle fgBundleForConstructedKeys= getBundle(MESSAGE_BUNDLE);
    
    //public static final String IMP_CODING_ACTION_SET = RuntimePlugin.IMP_RUNTIME + ".codingActionSet";
    //public static final String IMP_OPEN_ACTION_SET = RuntimePlugin.IMP_RUNTIME + ".openActionSet";

    public CeylonEditor() {
        // SMS 4 Apr 2007
        // Do not set preference store with store obtained from plugin; one is
        // already defined for the parent text editor and populated with relevant
        // preferences
        // setPreferenceStore(CeylonPlugin.getInstance().getPreferenceStore());
        setSourceViewerConfiguration(createSourceViewerConfiguration());
        setRangeIndicator(new CeylonRangeIndicator());
        configureInsertMode(SMART_INSERT, true);
        setInsertMode(SMART_INSERT);
        problemMarkerManager= new ProblemMarkerManager();
	}

	static String[][] getFences() {
		return new String[][] { { "(", ")" }, { "[", "]" }, { "{", "}" } };
	}
	
	public synchronized void pauseBackgroundParsing() {
		backgroundParsingPaused = true;
	}
    
	public synchronized void unpauseBackgroundParsing() {
		backgroundParsingPaused = false;
	}
	
	public synchronized boolean isBackgroundParsingPaused() {
		return backgroundParsingPaused;
	}
	
    /**
     * Sub-classes may override this method to extend the behavior provided by IMP's
     * standard StructuredSourceViewerConfiguration.
     * @return the StructuredSourceViewerConfiguration to use with this editor
     */
    protected CeylonSourceViewerConfiguration createSourceViewerConfiguration() {
    	return new CeylonSourceViewerConfiguration(getPreferenceStore(), this);
    }

    public IPreferenceStore getPrefStore() {
    	return super.getPreferenceStore();
    }

    @SuppressWarnings("rawtypes")
    public Object getAdapter(Class required) {
        if (IContentOutlinePage.class.equals(required)) {
            return getOutlinePage();
        }
        if (IToggleBreakpointsTarget.class.equals(required)) {
            return getToggleBreakpointAdapter();
        }
        /*if (IContextProvider.class.equals(required)) {
            return IMPHelp.getHelpContextProvider(this, fLanguageServiceManager, IMP_EDITOR_CONTEXT);
        }*/
        return super.getAdapter(required);
    }

	public Object getToggleBreakpointAdapter() {
		if (toggleBreakpointTarget == null) {
		    toggleBreakpointTarget = new ToggleBreakpointAdapter();
		}
		return toggleBreakpointTarget;
	}

	public Object getOutlinePage() {
		if (outlinePage == null) {
		    outlinePage = new CeylonOutlinePage(getParseController(),
		            new CeylonOutlineBuilder());
		    parserScheduler.addModelListener(outlinePage);
		    getSourceViewer().getTextWidget().addCaretListener(outlinePage);
		    //myOutlinePage.update(parseController);
		 }
		 return outlinePage;
	}

    protected void createActions() {
        super.createActions();

        final ResourceBundle bundle= getBundle(MESSAGE_BUNDLE);
        
        Action action= new ContentAssistAction(bundle, "ContentAssistProposal.", this);
        action.setActionDefinitionId(CONTENT_ASSIST_PROPOSALS);
        setAction("ContentAssistProposal", action);
        markAsStateDependentAction("ContentAssistProposal", true);

        toggleBreakpointAction= new ToggleBreakpointAction(this, 
        		getDocumentProvider().getDocument(getEditorInput()), 
        		getVerticalRuler());
        setAction("ToggleBreakpoint", action);
        
        enableDisableBreakpointAction= new RulerEnableDisableBreakpointAction(this, 
        		getVerticalRuler());
        setAction("ToggleBreakpoint", action);

        action= new TextOperationAction(bundle, "Format.", this, 
        		CeylonSourceViewer.FORMAT);
        action.setActionDefinitionId(FORMAT);
        setAction("Format", action);
        markAsStateDependentAction("Format", true);
        markAsSelectionDependentAction("Format", true);
        getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.FORMAT_ACTION);

        action= new TextOperationAction(bundle, "ShowOutline.", this, 
        		CeylonSourceViewer.SHOW_OUTLINE, true /* runsOnReadOnly */);
        action.setActionDefinitionId(SHOW_OUTLINE);
        setAction(SHOW_OUTLINE, action);
        getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.SHOW_OUTLINE_ACTION);

        action= new TextOperationAction(bundle, "ToggleComment.", this, 
        		CeylonSourceViewer.TOGGLE_COMMENT);
        action.setActionDefinitionId(TOGGLE_COMMENT);
        setAction(TOGGLE_COMMENT, action);
        getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.TOGGLE_COMMENT_ACTION);

        action= new TextOperationAction(bundle, "CorrectIndentation.", this, 
        		CeylonSourceViewer.CORRECT_INDENTATION);
        action.setActionDefinitionId(CORRECT_INDENTATION);
        setAction(CORRECT_INDENTATION, action);

        action= new GotoMatchingFenceAction(this);
        action.setActionDefinitionId(GOTO_MATCHING_FENCE);
        setAction(GOTO_MATCHING_FENCE, action);

        action= new GotoPreviousTargetAction(this);
        action.setActionDefinitionId(GOTO_PREVIOUS_TARGET);
        setAction(GOTO_PREVIOUS_TARGET, action);

        action= new GotoNextTargetAction(this);
        action.setActionDefinitionId(GOTO_NEXT_TARGET);
        setAction(GOTO_NEXT_TARGET, action);

        action= new SelectEnclosingAction(this);
        action.setActionDefinitionId(SELECT_ENCLOSING);
        setAction(SELECT_ENCLOSING, action);

        action= new RestorePreviousSelectionAction(this);
        action.setActionDefinitionId(RESTORE_PREVIOUS);
        setAction(RESTORE_PREVIOUS, action);

    	action= new TextOperationAction(bundle, "ShowHierarchy.", this, 
    			CeylonSourceViewer.SHOW_HIERARCHY, true);
        action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_HIERARCHY);
        setAction(EditorActionIds.SHOW_CEYLON_HIERARCHY, action);

    	action= new TextOperationAction(bundle, "ShowCode.", this, 
    			CeylonSourceViewer.SHOW_CODE, true);
        action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_CODE);
        setAction(EditorActionIds.SHOW_CEYLON_CODE, action);
        
        action= new TerminateStatementAction(this);
        action.setActionDefinitionId(EditorActionIds.TERMINATE_STATEMENT);
        setAction(EditorActionIds.TERMINATE_STATEMENT, action);

        foldingActionGroup= new FoldingActionGroup(this, this.getSourceViewer());
        
        getAction(ITextEditorActionConstants.SHIFT_LEFT)
            .setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
            		.getDescriptor(CeylonResources.SHIFT_LEFT));
        getAction(ITextEditorActionConstants.SHIFT_RIGHT)
            .setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
        		.getDescriptor(CeylonResources.SHIFT_RIGHT));
        
        IAction qaa=getAction(ITextEditorActionConstants.QUICK_ASSIST);
        qaa.setImageDescriptor(CeylonPlugin.getInstance().getImageRegistry()
    		    .getDescriptor(CeylonResources.QUICK_ASSIST));
        qaa.setText("Quick Fix/Assist");
        
    }
    
    @Override
    protected void createNavigationActions() {
        super.createNavigationActions();
        
        final StyledText textWidget= getSourceViewer().getTextWidget();

        /*IAction action= new SmartLineStartAction(textWidget, false);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_START);
        editor.setAction(ITextEditorActionDefinitionIds.LINE_START, action);

        action= new SmartLineStartAction(textWidget, true);
        action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_LINE_START);
        editor.setAction(ITextEditorActionDefinitionIds.SELECT_LINE_START, action);*/

        setAction(WORD_PREVIOUS, new NavigatePreviousSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL);

        setAction(WORD_NEXT, new NavigateNextSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL);

        setAction(SELECT_WORD_PREVIOUS, new SelectPreviousSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL);

        setAction(SELECT_WORD_NEXT, new SelectNextSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL);

        setAction(DELETE_PREVIOUS_WORD, new DeletePreviousSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.BS, SWT.NULL);
        markAsStateDependentAction(DELETE_PREVIOUS_WORD, true);

        setAction(DELETE_NEXT_WORD, new DeleteNextSubWordAction());
        textWidget.setKeyBinding(SWT.CTRL | SWT.DEL, SWT.NULL);
        markAsStateDependentAction(DELETE_NEXT_WORD, true);
    }

    protected class NavigateNextSubWordAction 
    extends NextSubWordAction {
    	public NavigateNextSubWordAction() {
    		super(ST.WORD_NEXT);
    		setActionDefinitionId(WORD_NEXT);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
    	}
    }

    protected class NavigatePreviousSubWordAction 
    extends PreviousSubWordAction {
    	public NavigatePreviousSubWordAction() {
    		super(ST.WORD_PREVIOUS);
    		setActionDefinitionId(WORD_PREVIOUS);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
    	}
    }

    protected abstract class NextSubWordAction 
    extends TextNavigationAction {

    	protected CeylonWordIterator fIterator= new CeylonWordIterator();

    	/**
    	 * Creates a new next sub-word action.
    	 *
    	 * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST.
    	 */
    	protected NextSubWordAction(int code) {
    		super(getSourceViewer().getTextWidget(), code);
    	}

    	@Override
    	public void run() {
    		// Check whether we are in a java code partition and the preference is enabled

    		final ISourceViewer viewer= getSourceViewer();
    		final IDocument document= viewer.getDocument();
    		try {
    			fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
    			int position= widgetOffset2ModelOffset(viewer, 
    					viewer.getTextWidget().getCaretOffset());
    			if (position == -1)
    				return;

    			int next= findNextPosition(position);
    			if (next != BreakIterator.DONE) {
    				setCaretPosition(next);
    				getTextWidget().showSelection();
    				fireSelectionChanged();
    			}
    		} 
    		catch (BadLocationException x) {
    			// ignore
    		}
    	}

    	/**
    	 * Finds the next position after the given position.
    	 *
    	 * @param position the current position
    	 * @return the next position
    	 */
    	protected int findNextPosition(int position) {
    		ISourceViewer viewer= getSourceViewer();
    		int widget= -1;
    		int next= position;
    		while (next != BreakIterator.DONE && widget == -1) { // XXX: optimize
    			next= fIterator.following(next);
    			if (next != BreakIterator.DONE)
    				widget= modelOffset2WidgetOffset(viewer, next);
    		}

    		IDocument document= viewer.getDocument();
    		LinkedModeModel model= LinkedModeModel.getModel(document, position);
    		if (model != null) {
    			LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
    			if (linkedPosition != null) {
    				int linkedPositionEnd= linkedPosition.getOffset() + linkedPosition.getLength();
    				if (position != linkedPositionEnd && linkedPositionEnd < next)
    					next= linkedPositionEnd;
    			} else {
    				LinkedPosition nextLinkedPosition= model.findPosition(new LinkedPosition(document, next, 0));
    				if (nextLinkedPosition != null) {
    					int nextLinkedPositionOffset= nextLinkedPosition.getOffset();
    					if (position != nextLinkedPositionOffset && nextLinkedPositionOffset < next)
    						next= nextLinkedPositionOffset;
    				}
    			}
    		}

    		return next;
    	}

    	/**
    	 * Sets the caret position to the sub-word boundary given with 
    	 * <code>position</code>.
    	 *
    	 * @param position Position where the action should move the caret
    	 */
    	protected abstract void setCaretPosition(int position);
    }

    protected abstract class PreviousSubWordAction 
    extends TextNavigationAction {

    	protected CeylonWordIterator fIterator= new CeylonWordIterator();

    	/**
    	 * Creates a new previous sub-word action.
    	 *
    	 * @param code Action code for the default operation. Must be an 
    	 * action code from @see org.eclipse.swt.custom.ST.
    	 */
    	protected PreviousSubWordAction(final int code) {
    		super(getSourceViewer().getTextWidget(), code);
    	}

    	@Override
    	public void run() {
    		// Check whether we are in a java code partition and the preference is enabled

    		final ISourceViewer viewer= getSourceViewer();
    		final IDocument document= viewer.getDocument();
    		try {
    			fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
    			int position= widgetOffset2ModelOffset(viewer, 
    					viewer.getTextWidget().getCaretOffset());
    			if (position == -1)
    				return;

    			int previous= findPreviousPosition(position);
    			if (previous != BreakIterator.DONE) {
    				setCaretPosition(previous);
    				getTextWidget().showSelection();
    				fireSelectionChanged();
    			}
    		} catch (BadLocationException x) {
    			// ignore - getLineOfOffset failed
    		}

    	}

    	/**
    	 * Finds the previous position before the given position.
    	 *
    	 * @param position the current position
    	 * @return the previous position
    	 */
    	protected int findPreviousPosition(int position) {
    		ISourceViewer viewer= getSourceViewer();
    		int widget= -1;
    		int previous= position;
    		while (previous != BreakIterator.DONE && widget == -1) { // XXX: optimize
    			previous= fIterator.preceding(previous);
    			if (previous != BreakIterator.DONE)
    				widget= modelOffset2WidgetOffset(viewer, previous);
    		}

    		IDocument document= viewer.getDocument();
    		LinkedModeModel model= LinkedModeModel.getModel(document, position);
    		if (model != null) {
    			LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
    			if (linkedPosition != null) {
    				int linkedPositionOffset= linkedPosition.getOffset();
    				if (position != linkedPositionOffset && previous < linkedPositionOffset)
    					previous= linkedPositionOffset;
    			} else {
    				LinkedPosition previousLinkedPosition= model.findPosition(new LinkedPosition(document, previous, 0));
    				if (previousLinkedPosition != null) {
    					int previousLinkedPositionEnd= previousLinkedPosition.getOffset() + previousLinkedPosition.getLength();
    					if (position != previousLinkedPositionEnd && previous < previousLinkedPositionEnd)
    						previous= previousLinkedPositionEnd;
    				}
    			}
    		}

    		return previous;
    	}

    	/**
    	 * Sets the caret position to the sub-word boundary given with <code>position</code>.
    	 *
    	 * @param position Position where the action should move the caret
    	 */
    	protected abstract void setCaretPosition(int position);
    }

    protected class SelectNextSubWordAction 
    extends NextSubWordAction {
    	public SelectNextSubWordAction() {
    		super(ST.SELECT_WORD_NEXT);
    		setActionDefinitionId(SELECT_WORD_NEXT);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		final ISourceViewer viewer= getSourceViewer();
    
    		final StyledText text= viewer.getTextWidget();
    		if (text != null && !text.isDisposed()) {
    
    			final Point selection= text.getSelection();
    			final int caret= text.getCaretOffset();
    			final int offset= modelOffset2WidgetOffset(viewer, position);
    
    			if (caret == selection.x)
    				text.setSelectionRange(selection.y, offset - selection.y);
    			else
    				text.setSelectionRange(selection.x, offset - selection.x);
    		}
    	}
    }
    
    protected class SelectPreviousSubWordAction 
    extends PreviousSubWordAction {
    	public SelectPreviousSubWordAction() {
    		super(ST.SELECT_WORD_PREVIOUS);
    		setActionDefinitionId(SELECT_WORD_PREVIOUS);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		final ISourceViewer viewer= getSourceViewer();
    
    		final StyledText text= viewer.getTextWidget();
    		if (text != null && !text.isDisposed()) {
    
    			final Point selection= text.getSelection();
    			final int caret= text.getCaretOffset();
    			final int offset= modelOffset2WidgetOffset(viewer, position);
    
    			if (caret == selection.x)
    				text.setSelectionRange(selection.y, offset - selection.y);
    			else
    				text.setSelectionRange(selection.x, offset - selection.x);
    		}
    	}
    }
    
    protected class DeleteNextSubWordAction 
    extends NextSubWordAction implements IUpdate {
    	public DeleteNextSubWordAction() {
    		super(ST.DELETE_WORD_NEXT);
    		setActionDefinitionId(DELETE_NEXT_WORD);
    	}
    	@Override
    	protected void setCaretPosition(final int position) {
    		if (!validateEditorInputState())
    			return;
    
    		final ISourceViewer viewer= getSourceViewer();
    		StyledText text= viewer.getTextWidget();
    		Point selection= viewer.getSelectedRange();
    		final int caret, length;
    		if (selection.y != 0) {
    			caret= selection.x;
    			length= selection.y;
    		} else {
    			caret= widgetOffset2ModelOffset(viewer, text.getCaretOffset());
    			length= position - caret;
    		}
    
    		try {
    			viewer.getDocument().replace(caret, length, "");
    		} catch (BadLocationException exception) {
    			// Should not happen
    		}
    	}
    	public void update() {
    		setEnabled(isEditorInputModifiable());
    	}
    }
    
    protected class DeletePreviousSubWordAction 
    extends PreviousSubWordAction implements IUpdate {
    	public DeletePreviousSubWordAction() {
    		super(ST.DELETE_WORD_PREVIOUS);
    		setActionDefinitionId(DELETE_PREVIOUS_WORD);
    	}
    	@Override
    	protected void setCaretPosition(int position) {
    		if (!validateEditorInputState())
    			return;
    
    		final int length;
    		final ISourceViewer viewer= getSourceViewer();
    		StyledText text= viewer.getTextWidget();
    		Point selection= viewer.getSelectedRange();
    		if (selection.y != 0) {
    			position= selection.x;
    			length= selection.y;
    		} else {
    			length= widgetOffset2ModelOffset(viewer, text.getCaretOffset()) - position;
    		}
    
    		try {
    			viewer.getDocument().replace(position, length, "");
    		} catch (BadLocationException exception) {
    			// Should not happen
    		}
    	}
    	public void update() {
    		setEnabled(isEditorInputModifiable());
    	}
    }

    protected void initializeKeyBindingScopes() {
        setKeyBindingScopes(new String[] { PLUGIN_ID + ".context" });
    }

    //private QuickMenuAction fQuickAccessAction;
    //private IHandlerActivation fQuickAccessHandlerActivation;
    //private IHandlerService fHandlerService;

    //private static final String QUICK_MENU_ID= "org.eclipse.imp.runtime.editor.refactor.quickMenu";

    /*private class RefactorQuickAccessAction extends QuickMenuAction {
        public RefactorQuickAccessAction() {
            super(QUICK_MENU_ID);
        }
        protected void fillMenu(IMenuManager menu) {
            //contributeRefactoringActions(menu);
        }
    }*/

    /*private void installQuickAccessAction() {
        fHandlerService= (IHandlerService) getSite().getService(IHandlerService.class);
        if (fHandlerService != null) {
            fQuickAccessAction= new RefactorQuickAccessAction();
            fQuickAccessHandlerActivation= fHandlerService.activateHandler(fQuickAccessAction.getActionDefinitionId(), 
            		new ActionHandler(fQuickAccessAction));
        }
    }*/


    protected boolean isOverviewRulerVisible() {
        return true;
    }

    protected void rulerContextMenuAboutToShow(IMenuManager menu) {
        addDebugActions(menu);

        super.rulerContextMenuAboutToShow(menu);

        //IMenuManager foldingMenu= new MenuManager("Folding", "projection");

        //menu.appendToGroup(GROUP_RULERS, foldingMenu);

        menu.appendToGroup(GROUP_RULERS, new Separator());
        
        IAction action;
//      action= getAction("FoldingToggle");
//      foldingMenu.add(action);
        action= getAction("FoldingExpandAll");
        menu.appendToGroup(GROUP_RULERS, action);
        //foldingMenu.add(action);
        action= getAction("FoldingCollapseAll");
        //foldingMenu.add(action);
        menu.appendToGroup(GROUP_RULERS, action);
        /*action= getAction("FoldingRestore");
        foldingMenu.add(action);
        action= getAction("FoldingCollapseMembers");
        foldingMenu.add(action);
        action= getAction("FoldingCollapseComments");
        foldingMenu.add(action);*/
    }

    private void addDebugActions(IMenuManager menu) {
        menu.add(toggleBreakpointAction);
        menu.add(enableDisableBreakpointAction);
    }

    /**
     * Sets the given message as error message to this editor's status line.
     *
     * @param msg message to be set
     */
    protected void setStatusLineErrorMessage(String msg) {
        IEditorStatusLine statusLine= (IEditorStatusLine) getAdapter(IEditorStatusLine.class);
        if (statusLine != null)
            statusLine.setMessage(true, msg, null);
    }

    /**
     * Sets the given message as message to this editor's status line.
     *
     * @param msg message to be set
     * @since 3.0
     */
    protected void setStatusLineMessage(String msg) {
        IEditorStatusLine statusLine= (IEditorStatusLine) getAdapter(IEditorStatusLine.class);
        if (statusLine != null)
            statusLine.setMessage(false, msg, null);
    }

    public ProblemMarkerManager getProblemMarkerManager() {
        return problemMarkerManager;
    }
    
    @Override
    protected void setTitleImage(Image titleImage) {
    	super.setTitleImage(titleImage);
    }

    public IDocumentProvider getDocumentProvider() {
    	if (isSrcArchive(getEditorInput())) {
    		//Note: I would prefer to register the
    		//document provider in plugin.xml but
    		//I don't know how to uniquely identity
    		//that a IURIEditorInput is a source
    		//archive there
    		if (sourceArchiveDocumentProvider==null) {
    			sourceArchiveDocumentProvider = new SourceArchiveDocumentProvider();
    		}
    		return sourceArchiveDocumentProvider;
    	}
    	else {
    		return super.getDocumentProvider();
    	}
    }
    
    public CeylonSourceViewer getCeylonSourceViewer() {
    	return (CeylonSourceViewer) super.getSourceViewer();
    }

    public void createPartControl(Composite parent) {
        
    	// Initialize the parse controller first, since the 
    	// initialization of other things (like the context 
    	// help support) might depend on it.
        initializeParseController();

        // Not sure why the "run the spell checker" pref would 
        // get set, but it does seem to, which gives lots of 
        // annoying squigglies all over the place...
        getPreferenceStore().setValue(PREFERENCE_SPELLING_ENABLED, false);

        super.createPartControl(parent);

        initiateServiceControllers();

        setTitleImageFromLanguageIcon();
        //setSourceFontFromPreference();
        setupBracketCloser();
        
        /*((IContextService) getSite().getService(IContextService.class))
                .activateContext(PLUGIN_ID + ".context");*/
        
        //CeylonPlugin.getInstance().getPreferenceStore().addPropertyChangeListener(colorChangeListener);
        currentTheme.getColorRegistry().addListener(colorChangeListener);
        updateFontAndCaret();
        currentTheme.getFontRegistry().addListener(fontChangeListener);
    }

    private void watchForSourceBuild() {        
        getWorkspace().addResourceChangeListener(buildListener= new IResourceChangeListener() {
            public void resourceChanged(IResourceChangeEvent event) {
                if (event.getType()==POST_BUILD && event.getBuildKind()!=CLEAN_BUILD) {
                	scheduleParsing();
                }
            }
        }, IResourceChangeEvent.POST_BUILD);
    }

    public synchronized void scheduleParsing() {
    	if (parserScheduler!=null && !backgroundParsingPaused) {
    		parserScheduler.cancel();
    		parserScheduler.schedule(REPARSE_SCHEDULE_DELAY);
    	}
    }

    private void initializeParseController() {
        IEditorInput editorInput= getEditorInput();
        IFile file = getFile(editorInput);
        IPath filePath = getPath(editorInput);
        parseController = new CeylonParseController();
        IProject project= file!=null && file.exists() ? file.getProject() : null;
        parseController.initialize(filePath, project, annotationCreator);
    }

    private void watchDocument() {
        getSourceViewer().getDocument()
                .addDocumentListener(documentListener= new IDocumentListener() {
            public void documentAboutToBeChanged(DocumentEvent event) {}
            public void documentChanged(DocumentEvent event) {
            	synchronized (CeylonEditor.this) {
            		if (parserScheduler!=null && !backgroundParsingPaused) {
            			parserScheduler.cancel();
            			parserScheduler.schedule(REPARSE_SCHEDULE_DELAY);
            		}
				}
            }
        });
    }

    /*private class BracketInserter implements VerifyKeyListener {
        private final Map<String,String> fFencePairs= new HashMap<String, String>();
        private final String fOpenFences;
        private final Map<Character,Boolean> fCloseFenceMap= new HashMap<Character, Boolean>();
//      private final String CATEGORY= toString();
//      private IPositionUpdater fUpdater= new ExclusivePositionUpdater(CATEGORY);

        public BracketInserter() {
            String[][] pairs= fLanguageServiceManager.getParseController().getSyntaxProperties().getFences();
            StringBuilder sb= new StringBuilder();
            for(int i= 0; i < pairs.length; i++) {
                sb.append(pairs[i][0]);
                fFencePairs.put(pairs[i][0], pairs[i][1]);
            }
            fOpenFences= sb.toString();
        }

        public void setCloseFenceEnabled(char openingFence, boolean enabled) {
            fCloseFenceMap.put(openingFence, enabled);
        }

        public void setCloseFencesEnabled(boolean enabled) {
            for(int i= 0; i < fOpenFences.length(); i++) {
                fCloseFenceMap.put(fOpenFences.charAt(i), enabled);
            }
        }

        public void verifyKey(VerifyEvent event) {
            // early pruning to slow down normal typing as little as possible
            if (!event.doit || getInsertMode() != SMART_INSERT)
                return;

            if (fOpenFences.indexOf(event.character) < 0) {
                return;
            }

            final ISourceViewer sourceViewer= getSourceViewer();
            IDocument document= sourceViewer.getDocument();

            final Point selection= sourceViewer.getSelectedRange();
            final int offset= selection.x;
            final int length= selection.y;

            try {
//              IRegion startLine= document.getLineInformationOfOffset(offset);
//              IRegion endLine= document.getLineInformationOfOffset(offset + length);

                // TODO Ask the parser/scanner whether the close fence is valid here
                // (i.e. whether it would recover by inserting the matching close fence character)
                // Right now, naively insert the closing fence regardless.

                ITypedRegion partition= TextUtilities.getPartition(document, getSourceViewerConfiguration().getConfiguredDocumentPartitioning(sourceViewer), offset, true);
                if (!IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType()))
                    return;

                if (!validateEditorInputState())
                    return;

                final String inputStr= new String(new char[] { event.character });
                final String closingFence= fFencePairs.get(inputStr);
                final StringBuffer buffer= new StringBuffer();
                buffer.append(inputStr);
                buffer.append(closingFence);

                document.replace(offset, length, buffer.toString());
                sourceViewer.setSelectedRange(offset + inputStr.length(), 0);

                event.doit= false;
            } 
            catch (BadLocationException e) {
                e.printStackTrace();
            }
        }
    }*/

    //private BracketInserter fBracketInserter;
    //private final String CLOSE_FENCES= PreferenceConstants.EDITOR_CLOSE_FENCES;

    private void setupBracketCloser() {
        // Bug #536: Disable for now, until we can be more intelligent 
        //about when to overwrite an existing (subsequent) close-fence char.
        /*IParseController parseController = fLanguageServiceManager.getParseController();
        if (parseController == null || parseController.getSyntaxProperties() == null || 
        		parseController.getSyntaxProperties().getFences() == null) {
            return;
        }

        //Preference key for automatically closing brackets and parenthesis 
        boolean closeFences= fLangSpecificPrefs.getBooleanPreference(CLOSE_FENCES); // false if no lang-specific setting

        fBracketInserter= new BracketInserter();
        fBracketInserter.setCloseFencesEnabled(closeFences);

        ISourceViewer sourceViewer= getSourceViewer();
        if (sourceViewer instanceof ITextViewerExtension)
            ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(fBracketInserter);*/
    }

    /**
     * The following listener is intended to detect when the document associated
     * with this editor changes its identity, which happens when, e.g., the
     * underlying resource gets moved or renamed.
     */
    private IPropertyListener fEditorInputPropertyListener = new IPropertyListener() {
        public void propertyChanged(Object source, int propId) {
            if (source == CeylonEditor.this && propId == IEditorPart.PROP_INPUT) {
                IDocument oldDoc= getParseController().getDocument();
                IDocument curDoc= getDocumentProvider().getDocument(getEditorInput()); 
                if (curDoc!=oldDoc) {
                    // Need to unwatch the old document and watch the new document
                    oldDoc.removeDocumentListener(documentListener);
                    curDoc.addDocumentListener(documentListener);
                }
            }
        }
    };

    private void watchForSourceMove() {
        // We need to see when the editor input changes, so we can watch the new document
        addPropertyListener(fEditorInputPropertyListener);
        getWorkspace().addResourceChangeListener(moveListener= new IResourceChangeListener() {
        	public void resourceChanged(IResourceChangeEvent event) {
        		if (event.getType()==IResourceChangeEvent.POST_CHANGE) {
        			IProject project = parseController.getProject();
        			if (project!=null) { //things extrenal to the workspace don't move
        				IPath oldWSRelPath= project.getFullPath().append(parseController.getPath());
        				IResourceDelta rd= event.getDelta().findMember(oldWSRelPath);
        				if (rd != null) {
        					if ((rd.getFlags() & IResourceDelta.MOVED_TO) == IResourceDelta.MOVED_TO) {
        						// The net effect of the following is to re-initialize() the IParseController with the new path
        						IPath newPath= rd.getMovedToPath();
        						IPath newProjRelPath= newPath.removeFirstSegments(1);
        						String newProjName= newPath.segment(0);
        						IProject proj= project.getName().equals(newProjName) ? 
        								project : project.getWorkspace().getRoot()
        							            .getProject(newProjName);
        						// Tell the IParseController about the move - it caches the path
        						// fParserScheduler.cancel(); // avoid a race condition if ParserScheduler was starting/in the middle of a run
        						parseController.initialize(newProjRelPath, proj, annotationCreator);
        					}
        				}
        			}
        		}
            }
        });
    }

    /*private void setSourceFontFromPreference() {
        String fontName = WorkbenchPlugin.getDefault().getPreferenceStore()
        		.getString(JFaceResources.TEXT_FONT);
        FontRegistry fontRegistry= CeylonPlugin.getInstance().getFontRegistry();
        if (!fontRegistry.hasValueFor(fontName)) {
            fontRegistry.put(fontName, PreferenceConverter.readFontData(fontName));
        }
        Font sourceFont= fontRegistry.get(fontName);
        if (sourceFont!=null) {
            getSourceViewer().getTextWidget().setFont(sourceFont);
        }
    }*/

    private void initiateServiceControllers() {
        try {
            final CeylonSourceViewer sourceViewer = (CeylonSourceViewer) getSourceViewer();
                        
            annotationUpdater= new IProblemChangedListener() {
                public void problemsChanged(IResource[] changedResources, 
                		boolean isMarkerChange) {
                    // Remove annotations that were resolved by changes to 
                	// other resources. 
                	// TODO: It would be better to match the markers to the 
                	// annotations, and decide which annotations to remove.
                    scheduleParsing();
                }
            };
            problemMarkerManager.addListener(annotationUpdater);
            
            editorIconUpdater= new EditorIconUpdater(this);
            problemMarkerManager.addListener(editorIconUpdater);

			parserScheduler= new CeylonParserScheduler(parseController, this, 
{'fraction_non_alphanumeric': 0.09218702594586091, 'fraction_numerical': 0.0011911049183643658, 'mean_word_length': 3.2410407929851317, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4049911', 'n_tokens_mistral': 25759, 'n_tokens_neox': 24532, 'n_words': 4927}