blob: d8172bb4802e71a1fe8a20fceb71c63a7664a558 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2015 itemis AG (http://www.itemis.eu) and others.
* 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:
* Vladimir Piskarev (1C) - adaptation of XtextReconciler code
*******************************************************************************/
package org.eclipse.handly.xtext.ui.editor;
import static org.eclipse.xtext.ui.editor.XtextSourceViewerConfiguration.XTEXT_TEMPLATE_POS_CATEGORY;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.handly.internal.xtext.ui.Activator;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ICompletionListener;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.reconciler.IReconciler;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.source.ContentAssistantFacade;
import org.eclipse.jface.text.source.ISourceViewerExtension4;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.ui.editor.model.IXtextDocumentContentObserver;
import org.eclipse.xtext.ui.editor.reconciler.XtextReconciler;
import org.eclipse.xtext.util.CancelIndicator;
/**
* Adapted from <code>org.eclipse.xtext.ui.editor.reconciler.XtextReconciler</code>.
* Customized for Handly reconciling story. Should be used together with
* {@link HandlyXtextDocument}.
*
* @noextend This class is not intended to be extended by clients.
*/
// NOTE: This class extends XtextReconciler to retain assignment compatibility.
// The actual implementation is delegated to the inner class InternalReconciler
public class HandlyXtextReconciler
extends XtextReconciler
{
private final InternalReconciler delegate = new InternalReconciler();
public HandlyXtextReconciler()
{
super(null);
}
@Override
public void install(ITextViewer textViewer)
{
delegate.install(textViewer);
}
@Override
public void uninstall()
{
delegate.uninstall();
}
@Override
public IReconcilingStrategy getReconcilingStrategy(String contentType)
{
return delegate.getReconcilingStrategy(contentType);
}
@Override
public void setReconcilingStrategy(IReconcilingStrategy strategy)
{
}
@Override
public void setEditor(XtextEditor editor)
{
}
@Override
public void setDelay(int delay)
{
// delegate is null when this method is called from the super constructor
if (delegate == null)
return;
delegate.setDelay(delay);
}
@Override
public void forceReconcile()
{
delegate.forceReconcile();
}
@Override
public boolean shouldSchedule()
{
// #schedule() should never be called for this job
throw new AssertionError(); // fail on a schedule request
}
private static class InternalReconciler
extends Job
implements IReconciler
{
private boolean isInstalled;
private boolean shouldInstallCompletionListener;
private volatile boolean paused;
private ITextViewer viewer;
private final TextInputListener textInputListener =
new TextInputListener();
private final DocumentListener documentListener =
new DocumentListener();
private int delay = 500;
public InternalReconciler()
{
super("Xtext Editor Reconciler"); //$NON-NLS-1$
setPriority(Job.SHORT);
setSystem(true);
}
public void setDelay(int delay)
{
this.delay = delay;
}
public void forceReconcile()
{
if (viewer == null)
return;
IDocument document = viewer.getDocument();
if (document instanceof HandlyXtextDocument)
((HandlyXtextDocument)document).reconcile(true);
}
@Override
public void install(ITextViewer textViewer)
{
if (!isInstalled)
{
viewer = textViewer;
IDocument document0 = viewer.getDocument();
viewer.addTextInputListener(textInputListener);
IDocument document = viewer.getDocument();
if (document instanceof HandlyXtextDocument
&& document == document0) // a bit of paranoia: document != document0 means the document has changed under us and the text input listener has already handled it
{
((HandlyXtextDocument)document).addXtextDocumentContentObserver(
documentListener);
}
if (viewer instanceof ISourceViewerExtension4)
{
ContentAssistantFacade facade =
((ISourceViewerExtension4)viewer).getContentAssistantFacade();
if (facade == null)
shouldInstallCompletionListener = true;
else
facade.addCompletionListener(documentListener);
}
isInstalled = true;
}
}
@Override
public void uninstall()
{
if (isInstalled)
{
viewer.removeTextInputListener(textInputListener);
IDocument document = viewer.getDocument();
if (document instanceof HandlyXtextDocument)
{
((HandlyXtextDocument)document).removeXtextDocumentContentObserver(
documentListener);
}
if (viewer instanceof ISourceViewerExtension4)
{
ContentAssistantFacade facade =
((ISourceViewerExtension4)viewer).getContentAssistantFacade();
facade.removeCompletionListener(documentListener);
}
isInstalled = false;
}
}
@Override
public IReconcilingStrategy getReconcilingStrategy(String contentType)
{
return null; // it's safe to return no strategy
}
@Override
public boolean belongsTo(Object family)
{
return XtextReconciler.class.getName().equals(family);
}
@Override
protected IStatus run(final IProgressMonitor monitor)
{
if (monitor.isCanceled() || paused)
return Status.CANCEL_STATUS;
IDocument document = viewer.getDocument();
if (document instanceof HandlyXtextDocument)
{
HandlyXtextDocument doc = (HandlyXtextDocument)document;
if (doc.needsReconciling())
{
doc.reconcile(false, new CancelIndicator()
{
public boolean isCanceled()
{
return monitor.isCanceled();
}
});
}
}
return Status.OK_STATUS;
}
private void handleDocumentChanged(DocumentEvent event)
{
cancel();
schedule(delay);
}
private void pause()
{
paused = true;
}
private void resume()
{
paused = false;
schedule(delay);
}
private class DocumentListener
implements IXtextDocumentContentObserver, ICompletionListener
{
private final IPositionUpdater templatePositionUpdater =
new TemplatePositionUpdater(XTEXT_TEMPLATE_POS_CATEGORY);
private volatile boolean sessionStarted = false;
@Override
public void documentAboutToBeChanged(DocumentEvent event)
{
}
@Override
public void documentChanged(DocumentEvent event)
{
handleDocumentChanged(event);
}
@Override
public boolean performNecessaryUpdates(Processor processor)
{
// Note: this method is always called with the doc's readLock held
boolean hadUpdates = false;
IDocument document = viewer.getDocument();
if (document instanceof HandlyXtextDocument && !paused)
{
HandlyXtextDocument doc = (HandlyXtextDocument)document;
try
{
if (doc.needsReconciling()) // this check is required to avoid constant rescheduling of ValidationJob
hadUpdates = doc.reconcile(processor);
}
catch (Throwable e)
{
Activator.log(Activator.createErrorStatus(
"Error while forcing reconciliation", e)); //$NON-NLS-1$
}
}
if (sessionStarted && !paused)
{
pause();
}
return hadUpdates;
}
@Override
public boolean hasPendingUpdates()
{
IDocument document = viewer.getDocument();
if (document instanceof HandlyXtextDocument)
return ((HandlyXtextDocument)document).needsReconciling();
return false;
}
@Override
public void assistSessionStarted(ContentAssistEvent event)
{
IDocument document = viewer.getDocument();
document.addPositionCategory(XTEXT_TEMPLATE_POS_CATEGORY);
document.addPositionUpdater(templatePositionUpdater);
sessionStarted = true;
}
@Override
public void assistSessionEnded(ContentAssistEvent event)
{
sessionStarted = false;
IDocument document = viewer.getDocument();
document.removePositionUpdater(templatePositionUpdater);
try
{
document.removePositionCategory(
XTEXT_TEMPLATE_POS_CATEGORY);
}
catch (BadPositionCategoryException e)
{
}
resume();
}
@Override
public void selectionChanged(ICompletionProposal proposal,
boolean smartToggle)
{
}
}
private class TextInputListener
implements ITextInputListener
{
public void inputDocumentAboutToBeChanged(IDocument oldInput,
IDocument newInput)
{
if (oldInput instanceof HandlyXtextDocument)
{
((HandlyXtextDocument)oldInput).removeXtextDocumentContentObserver(
documentListener);
cancel();
}
}
@Override
public void inputDocumentChanged(IDocument oldInput,
IDocument newInput)
{
if (newInput instanceof HandlyXtextDocument)
{
((HandlyXtextDocument)newInput).addXtextDocumentContentObserver(
documentListener);
schedule(delay);
}
if (shouldInstallCompletionListener)
{
ContentAssistantFacade facade =
((ISourceViewerExtension4)viewer).getContentAssistantFacade();
if (facade != null)
facade.addCompletionListener(documentListener);
shouldInstallCompletionListener = false;
}
}
}
// Initially copied from <code>org.eclipse.xtext.ui.editor.reconciler.TemplatePositionUpdater</code>
// only because the original is a package-private class.
private static class TemplatePositionUpdater
extends DefaultPositionUpdater
{
public TemplatePositionUpdater(String category)
{
super(category);
}
@Override
protected void adaptToInsert()
{
int myStart = fPosition.offset;
int myEnd = fPosition.offset + fPosition.length - 1;
myEnd = Math.max(myStart, myEnd);
int yoursStart = fOffset;
int yoursEnd = fOffset + fReplaceLength - 1;
yoursEnd = Math.max(yoursStart, yoursEnd);
if (myEnd < yoursStart)
return;
fPosition.length += fReplaceLength;
}
}
}
}