/*******************************************************************************
* Copyright (c) Gil Barash - chookapp@yahoo.com
* 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:
* Gil Barash - initial API and implementation
*
* Thanks to:
* emil.crumhorn@gmail.com - Some of the code was copied from the
* "eclipsemissingfeatrues" plugin.
*******************************************************************************/
package com.chookapp.org.bracketeer.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IPaintPositionManager;
import org.eclipse.jface.text.IPainter;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension2;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.JFaceTextUtil;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CaretEvent;
import org.eclipse.swt.custom.CaretListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.services.IDisposable;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.SimpleMarkerAnnotation;
import com.chookapp.org.bracketeer.Activator;
import com.chookapp.org.bracketeer.common.BracketsPair;
import com.chookapp.org.bracketeer.common.Hint;
import com.chookapp.org.bracketeer.common.SingleBracket;
import com.chookapp.org.bracketeer.core.ProcessorConfiguration.HintConfiguration;
import com.chookapp.org.bracketeer.extensionpoint.BracketeerProcessor;
public class BracketsHighlighter implements CaretListener, Listener,
PaintListener, IDisposable, IPainter, IProcessingContainerListener,
IProcessorConfigurationListener, FocusListener
{
private ISourceViewer _sourceViewer;
private StyledText _textWidget;
private ProcessingThread _processingThread;
private IDocument _doc;
private ProcessorConfiguration _conf;
private IResource _resource;
private IAnnotationModel _annotationModel;
private Map<Annotation, Position> _annotationMap;
private boolean _isActive;
private List<PaintableBracket> _hoveredPairsToPaint;
private List<PaintableBracket> _surroundingPairsToPaint;
private List<PaintableBracket> _singleBracketsToPaint;
private List<PaintableHint> _hintsToPaint;
private PaintableHint _hoveredHintToPaint;
private Point m_hoverEntryPoint;
private Popup _popup;
private PaintableHint _mousePointingAtHint;
private SingleBracket _mousePointingAtBracket;
private boolean _mousePointerHand;
private int _caretOffset;
private int m_hyperlinkModifiers;
public BracketsHighlighter()
{
_sourceViewer = null;
_processingThread = null;
_textWidget = null;
_conf = null;
_doc = null;
_resource = null;
_annotationModel = null;
_annotationMap = new HashMap<Annotation, Position>();
_isActive = false;
_hoveredPairsToPaint = new LinkedList<PaintableBracket>();
_surroundingPairsToPaint = new LinkedList<PaintableBracket>();
_singleBracketsToPaint = new LinkedList<PaintableBracket>();
_hintsToPaint = new ArrayList<PaintableHint>();
m_hoverEntryPoint = null;
_hoveredHintToPaint = null;
_popup = null;
_mousePointingAtHint = null;
_mousePointingAtBracket = null;
_mousePointerHand = false;
}
@Override
public void dispose()
{
clearPopup();
if( _sourceViewer == null )
return;
_conf.removeListener(this);
deactivate(false);
ITextViewerExtension2 extension = (ITextViewerExtension2) _sourceViewer;
extension.removePainter(this);
if (_processingThread != null)
{
_processingThread.getBracketContainer().removeListener(this);
_processingThread.dispose();
_processingThread = null;
}
_sourceViewer = null;
_textWidget = null;
}
/************************************************************
* public methods
* @param part
* @param part
************************************************************/
public void Init(BracketeerProcessor processor, IEditorPart part, IDocument doc,
ITextViewer textViewer, ProcessorConfiguration conf)
{
_sourceViewer = (ISourceViewer) textViewer;
_textWidget = _sourceViewer.getTextWidget();
_conf = conf;
processor.setHintConf(conf.getHintConfiguration());
_doc = doc;
boolean editable = _textWidget.getEditable();
_resource = (IResource) part.getEditorInput().getAdapter(IResource.class);
if (_resource == null && editable)
Activator.log(Messages.BracketsHighlighter_UnableToGetResource);
ITextEditor editor = (ITextEditor) part.getAdapter(ITextEditor.class);
if (editor == null)
{
Activator.log(Messages.BracketsHighlighter_UnableToGetEditor);
}
else
{
IDocumentProvider provider = editor.getDocumentProvider();
_annotationModel = provider.getAnnotationModel(editor.getEditorInput());
}
_processingThread = new ProcessingThread(doc, processor);
_processingThread.getBracketContainer().addListener(this);
_conf.addListener(this);
ITextViewerExtension2 extension = (ITextViewerExtension2) textViewer;
extension.addPainter(this);
m_hyperlinkModifiers = _conf.getGeneralConfiguration().getHyperlinkModifiers();
}
public ISourceViewer getSourceViewer()
{
return _sourceViewer;
}
public ProcessorConfiguration getConfiguration()
{
return _conf;
}
/************************************************************
* listeners
************************************************************/
@Override
public void caretMoved(CaretEvent event)
{
_caretOffset = getCurrentCaretOffset();
caretMovedTo(_caretOffset);
}
/*
* Events:
* - MouseHover
* - MouseMove
* - MouseDown
* - KeyDown
* - KeyUp
*
* (non-Javadoc)
* @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
*/
@Override
public void handleEvent(Event event) {
switch( event.type)
{
case SWT.MouseHover:
// hovering disabled when in "hyperlink mode"
if( (event.stateMask & SWT.MODIFIER_MASK) == m_hyperlinkModifiers )
return;
try
{
int caret = getDocCarretAdvanced(null, event.x, event.y);
if( mouseHoverAt(_textWidget, caret) )
{
m_hoverEntryPoint = new Point(event.x, event.y);
}
}
catch(IllegalArgumentException e)
{
}
catch(Exception e )
{
Activator.log(e);
}
break;
case SWT.MouseMove:
if( (event.stateMask & SWT.MODIFIER_MASK) == m_hyperlinkModifiers )
{
if( _textWidget.isFocusControl() )
{
mousePointingAt(event.x, event.y);
updateMousePointer();
}
}
if( m_hoverEntryPoint == null )
break;
if( getDistanceBetween(new Point(event.x, event.y), m_hoverEntryPoint) > 20 )
{
caretMovedTo(getCurrentCaretOffset());
m_hoverEntryPoint = null;
}
break;
case SWT.MouseDown:
if( _mousePointingAtHint != null )
{
Hint hint = _processingThread.getBracketContainer().getHint(_mousePointingAtHint.getPosition().getOffset());
if( hint == null )
{
Activator.log(Messages.BracketsHighlighter_ErrHintNotFound);
break;
}
jumpToPosition(hint.getOriginPosition());
}
if( _mousePointingAtBracket != null )
{
List<BracketsPair> pairs = _processingThread.getBracketContainer().getMatchingPairs(_mousePointingAtBracket.getPosition().getOffset(), 1);
if( pairs.size() == 0 || pairs.size() > 1 )
{
Activator.log(Messages.BracketsHighlighter_ErrPairNotFound);
break;
}
BracketsPair pair = pairs.get(0);
Position pos = null;
if( pair.getOpeningBracket().equals(_mousePointingAtBracket) )
pos = pair.getClosingBracket().getPosition();
if( pair.getClosingBracket().equals(_mousePointingAtBracket) )
pos = pair.getOpeningBracket().getPosition();
jumpToPosition(pos);
}
break;
case SWT.KeyDown:
if( (event.keyCode | event.stateMask) == m_hyperlinkModifiers )
{
/* clearing hovered pairs */
if( m_hoverEntryPoint != null )
{
caretMovedTo(getCurrentCaretOffset());
m_hoverEntryPoint = null;
}
Display display = _textWidget.getDisplay();
Point point = display.getCursorLocation();
point = display.map(null, _textWidget, point);
mousePointingAt(point.x, point.y);
updateMousePointer();
}
else
{
clearHyperlink();
updateMousePointer();
}
break;
case SWT.KeyUp:
if( (event.keyCode & m_hyperlinkModifiers) > 0 ) // is one of the modifiers being released?
{
clearHyperlink();
updateMousePointer();
}
break;
default:
Assert.isTrue(false, Messages.BracketsHighlighter_ErrUnexpectedEvent + event.type);
}
}
private int getDocCarretAdvanced(GC outerGc, int x, int y)
{
int caret = -1;
GC gc = null;
if( outerGc == null )
gc = new GC(_textWidget);
else
gc = outerGc;
try
{
int charWidth = gc.getFontMetrics().getAverageCharWidth();
caret = _textWidget.getOffsetAtLocation(new Point(x + (charWidth/2), y));
caret = ((ProjectionViewer)_sourceViewer).widgetOffset2ModelOffset(caret);
if( caret > 0 )
caret--;
}
catch(IllegalArgumentException e)
{
caret = -1;
}
finally
{
if(outerGc == null)
gc.dispose();
}
if( caret == -1 )
{
try
{
caret = _textWidget.getOffsetAtLocation(new Point(x, y));
caret = ((ProjectionViewer)_sourceViewer).widgetOffset2ModelOffset(caret);
if( caret > 0 )
caret--;
}
catch(IllegalArgumentException e)
{
caret = -1;
}
}
return caret;
}
@Override
public void focusGained(FocusEvent e)
{
}
@Override
public void focusLost(FocusEvent e)
{
clearHyperlink();
updateMousePointer();
}
@Override
public void paintControl(PaintEvent event)
{
try
{
IRegion region = computeClippingRegion(event);
if (region == null)
return;
int startOfset = region.getOffset();
int length = region.getLength();
for (PaintableObject paintObj : _singleBracketsToPaint)
{
if(paintObj.getPosition().overlapsWith(startOfset, length))
paintObj.paint(event.gc, _textWidget, _sourceViewer.getDocument(),
getWidgetRange(paintObj.getPosition().getOffset(),
paintObj.getPosition().getLength()),
null);
}
List<PaintableBracket> pairsToPaint;
if( _hoveredPairsToPaint.isEmpty() )
pairsToPaint = _surroundingPairsToPaint;
else
pairsToPaint = _hoveredPairsToPaint;
for (PaintableObject paintObj : pairsToPaint)
{
if(paintObj.getPosition().overlapsWith(startOfset, length))
paintObj.paint(event.gc, _textWidget, _sourceViewer.getDocument(),
getWidgetRange(paintObj.getPosition().getOffset(),
paintObj.getPosition().getLength()),
null);
}
boolean hoveredHintPainted = false;
for (PaintableHint paintObj : _hintsToPaint)
{
if(_hoveredHintToPaint != null && _hoveredHintToPaint.getPosition().equals(paintObj.getPosition()))
{
paintObj = _hoveredHintToPaint;
hoveredHintPainted = true;
}
paintHint(paintObj, event);
}
if(!hoveredHintPainted && _hoveredHintToPaint != null)
{
paintHint(_hoveredHintToPaint, event);
}
}
catch (Exception e)
{
Activator.log(e);
}
}
private void paintHint(PaintableHint paintObj, PaintEvent event)
{
IRegion widgetRange = getWidgetRange(paintObj.getPosition().getOffset(),
paintObj.getPosition().getLength());
Rectangle widgetRect = paintObj.getWidgetRect(event.gc, _textWidget, _sourceViewer.getDocument(), widgetRange);
if( widgetRect != null && widgetRect.intersects(event.x, event.y, event.width, event.height) )
paintObj.paint(event.gc, _textWidget, _sourceViewer.getDocument(), widgetRange, widgetRect );
}
public ITextViewer getTextViewer()
{
return _sourceViewer;
}
@Override
public void configurationUpdated()
{
m_hyperlinkModifiers = _conf.getGeneralConfiguration().getHyperlinkModifiers();
boolean updated = false;
updated |= clearSurroundingPairsToPaint();
updated |= clearSingleBracketsToPaint();
rebuild(true, true, true, updated);
}
@Override
public void containerUpdated(boolean bracketsPairsTouched,
boolean singleBracketsTouched,
boolean hintsTouched)
{
rebuild( bracketsPairsTouched, singleBracketsTouched,
hintsTouched, false );
}
/************************************************************
* IPainter interface
************************************************************/
@Override
public void paint(int reason)
{
if(!_isActive)
{
if(_sourceViewer == null)
{
Activator.log(Messages.BracketsHighlighter_UnableToPaint_SourceViewer);
return;
}
_isActive = true;
StyledText st = _sourceViewer.getTextWidget();
st.addCaretListener(this);
st.addListener(SWT.MouseHover, this);
st.addListener(SWT.MouseMove, this);
st.addListener(SWT.MouseDown, this);
st.addListener(SWT.KeyDown, this);
st.addListener(SWT.KeyUp, this);
st.addPaintListener(this);
st.addFocusListener(this);
_caretOffset = getCurrentCaretOffset();
}
}
@Override
public void deactivate(boolean redraw)
{
if(!_isActive)
return;
_isActive = false;
if( _sourceViewer == null )
return;
StyledText st = _sourceViewer.getTextWidget();
if( st == null )
return;
st.removeCaretListener(this);
st.removeListener(SWT.MouseHover, this);
st.removeListener(SWT.MouseMove, this);
st.removeListener(SWT.MouseDown, this);
st.removeListener(SWT.KeyDown, this);
st.removeListener(SWT.KeyUp, this);
st.removePaintListener(this);
st.removeFocusListener(this);
}
@Override
public void setPositionManager(IPaintPositionManager manager)
{
}
/************************************************************
* the work itself
************************************************************/
private void rebuild(boolean bracketsPairsTouched,
boolean singleBracketsTouched,
boolean hintsTouched,
boolean alwaysRedraw)
{
boolean update = alwaysRedraw;
if( bracketsPairsTouched )
{
update |= updateSurroundingPairsToPaint(_caretOffset);
update |= clearHoveredPairsToPaint();
}
if( singleBracketsTouched )
update |= updateSingleBrackets();
// I'm ignoring 'hintsTouched' because the "line distance" might have been modified
update |= updateHints();
update |= clearHoveredHint();
if(update)
{
// TODO: optimize? (redraw only the needed sections)
_textWidget.getDisplay().asyncExec(new Runnable()
{
@Override
public void run()
{
if( _textWidget != null )
_textWidget.redraw();
}
});
}
}
private void updateMousePointer()
{
if( _mousePointingAtHint != null || _mousePointingAtBracket != null )
{
if( _mousePointerHand )
return;
_textWidget.setCursor(_textWidget.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
_mousePointerHand = true;
}
else
{
if( !_mousePointerHand )
return;
_textWidget.setCursor(null);
_mousePointerHand = false;
}
}
private void clearHyperlink()
{
if( _mousePointingAtHint != null )
{
// GC gc = new GC(_textWidget);
//
// try
// {
// IRegion widgetRange = getWidgetRange(_mousePointingAtHint.getPosition().getOffset(),
// _mousePointingAtHint.getPosition().getLength());
// Rectangle rect = _mousePointingAtHint.getWidgetRect(gc, _textWidget,
// _sourceViewer.getDocument(), widgetRange);
//
// _mousePointingAtHint.setUnderline(false);
// if( rect != null )
// _textWidget.redraw(rect.x, rect.y, rect.width, rect.height, true);
// else
// _textWidget.redraw();
// _mousePointingAtHint = null;
// }
// finally
// {
// gc.dispose();
// }
// unoptimize... (I'm not sure that the code above, which works, doesn't take more time...)
_mousePointingAtHint.setUnderline(false);
_textWidget.redraw();
_mousePointingAtHint = null;
}
if( _mousePointingAtBracket != null )
{
clearHoveredPairsToPaint();
_textWidget.redraw();
_mousePointingAtBracket = null;
}
}
private void mousePointingAt(int x, int y)
{
int caret = -1;
GC gc = new GC(_textWidget);
try
{
caret = getDocCarretAdvanced(gc, x, y);
if( _mousePointingAtBracket != null )
{
Position pos = _mousePointingAtBracket.getPosition();
if( (pos != null) && (pos.getOffset() == caret) )
return;
_mousePointingAtBracket = null;
clearHoveredPairsToPaint();
clearHoveredHint();
clearPopup();
// TODO: optimize? (redraw only the needed sections)
_textWidget.redraw();
}
if( _mousePointingAtHint != null )
{
IRegion widgetRange = getWidgetRange(_mousePointingAtHint.getPosition().getOffset(),
_mousePointingAtHint.getPosition().getLength());
Rectangle rect = _mousePointingAtHint.getWidgetRect(gc, _textWidget,
_sourceViewer.getDocument(), widgetRange);
if( rect != null && rect.intersects(x, y, 1, 1) )
return;
_mousePointingAtHint.setUnderline(false);
_textWidget.redraw();
_mousePointingAtHint = null;
}
for (PaintableHint paintObj : _hintsToPaint)
{
IRegion widgetRange = getWidgetRange(paintObj.getPosition().getOffset(),
paintObj.getPosition().getLength());
Rectangle rect = paintObj.getWidgetRect(gc, _textWidget, _sourceViewer.getDocument(), widgetRange);
if( rect != null && rect.intersects(x, y, 1, 1) )
{
_mousePointingAtHint = paintObj;
_mousePointingAtHint.setUnderline(true);
_textWidget.redraw(rect.x, rect.y, rect.width, rect.height, true);
return;
}
}
}
finally
{
gc.dispose();
}
BracketeerProcessingContainer cont = _processingThread.getBracketContainer();
List<BracketsPair> pairs = cont.getMatchingPairs(caret, 1);
Assert.isTrue(pairs.size() <= 1);
if( pairs.size() == 0 )
return;
BracketsPair pair = pairs.get(0);
Position pos = pair.getOpeningBracket().getPosition();
if( (pos != null) && (pos.getOffset() == caret) )
_mousePointingAtBracket = pair.getOpeningBracket();
pos = pair.getClosingBracket().getPosition();
if( (pos != null) && (pos.getOffset() == caret) )
_mousePointingAtBracket = pair.getClosingBracket();
if( _mousePointingAtBracket == null )
{
Activator.log(Messages.BracketsHighlighter_ErrBracketNotFound);
return;
}
synchronized (_hoveredPairsToPaint)
{
addPaintableObjectsPairs(pairs, 0, 1, _hoveredPairsToPaint);
}
// TODO: optimize? (redraw only the needed sections)
_textWidget.redraw();
}
private void jumpToPosition(Position pos)
{
if( pos == null )
return;
_sourceViewer.setSelectedRange(pos.getOffset(), 0);
_sourceViewer.revealRange(pos.getOffset(), 0);
}
private void caretMovedTo(int caretOffset)
{
boolean update = updateSurroundingPairsToPaint(caretOffset);
update |= clearHoveredPairsToPaint();
update |= clearHoveredHint();
clearPopup();
if(update)
{
// TODO: optimize? (redraw only the needed sections)
_textWidget.redraw();
}
}
private boolean updateSurroundingPairsToPaint(int caretOffset)
{
if(!_conf.getPairConfiguration().isSurroundingPairsEnabled())
return clearSurroundingPairsToPaint();
BracketeerProcessingContainer cont = _processingThread.getBracketContainer();
List<BracketsPair> listOfPairs = cont.getPairsSurrounding(caretOffset);
/* excluding... */
String includedPairs= _conf.getPairConfiguration().getSurroundingPairsToInclude();
Iterator<BracketsPair> it = listOfPairs.iterator();
while(it.hasNext())
{
BracketsPair pair = it.next();
for( SingleBracket br : pair.getBrackets())
{
if( includedPairs.indexOf(br.getChar()) == -1 )
{
it.remove();
break;
}
}
if( pair.getDistanceBetweenBrackets()-1 < _conf.getPairConfiguration().getMinDistanceBetweenBrackets())
{
it.remove();
}
}
listOfPairs = sortPairs(listOfPairs);
listOfPairs = listOfPairs.subList(0, Math.min(_conf.getPairConfiguration().getSurroundingPairsCount(),
listOfPairs.size()));
// do nothing if _surroundingPairsToPaint is equal to listOfPairs
if(areEqualPairs(listOfPairs, _surroundingPairsToPaint))
return false;
clearSurroundingPairsToPaint();
synchronized (_surroundingPairsToPaint)
{
addPaintableObjectsPairs(listOfPairs, 0, 1, _surroundingPairsToPaint);
}
return true;
}
private boolean updateSingleBrackets()
{
BracketeerProcessingContainer cont = _processingThread.getBracketContainer();
List<SingleBracket> list = cont.getSingleBrackets();
// do nothing if _surroundingPairsToPaint is equal to listOfPairs
if(areEqualSingle(list, _singleBracketsToPaint))
return false;
clearSingleBracketsToPaint();
synchronized (_singleBracketsToPaint)
{
addPaintableObjectsSingles(list, _singleBracketsToPaint);
}
return true;
}
private boolean updateHints()
{
BracketeerProcessingContainer cont = _processingThread.getBracketContainer();
ArrayList<PaintableHint> hintsToPaint = new ArrayList<PaintableHint>();
HintConfiguration conf = _conf.getHintConfiguration();
IDocument doc = _doc;
for (Hint hint : cont.getHints())
{
String type = hint.getType();
if( !_conf.getHintConfiguration().isShowInEditor(type) )
continue;
int originLine, drawLine;
try
{
originLine = doc.getLineOfOffset(hint.getOriginPositionRaw().getOffset());
drawLine = doc.getLineOfOffset(hint.getHintPositionRaw().getOffset());
}
catch (BadLocationException e)
{
continue;
}
if( drawLine - originLine < conf.getMinLineDistance(type) )
continue;
PaintableHint pHint = new PaintableHint(hint.getHintPositionRaw(),
conf.getColor(type, true),
conf.getColor(type, false),
conf.isItalic(type),
conf.formatText(type, hint.getTxt()));
hintsToPaint.add(pHint);
}
if( _hintsToPaint.equals(hintsToPaint) )
return false;
synchronized (_hintsToPaint)
{
_hintsToPaint = hintsToPaint;
}
return true;
}
private List<BracketsPair> sortPairs(List<BracketsPair> listOfPairs)
{
List<BracketsPair> ret = new ArrayList<BracketsPair>(listOfPairs.size());
for (BracketsPair pair : listOfPairs)
{
int i = 0;
while( i < ret.size() )
{
if( ret.get(i).getOpeningBracket().getPositionRaw().offset <
pair.getOpeningBracket().getPositionRaw().offset )
{
break;
}
i++;
}
ret.add(i, pair);
}
return ret;
}
/*
* Return true iff the hover is not empty...
*/
private boolean mouseHoverAt(StyledText st, int origCaret)
{
boolean ret = markHoveredBrackets(origCaret);
ret |= showHoveredHint(origCaret);
ret |= showPopup(origCaret);
return ret;
}
private boolean showPopup(int origCaret)
{
clearPopup();
if( !_conf.getPairConfiguration().isPopupEnabled() )
return false;
try
{
if( _conf.getPairConfiguration().showPopupOnlyWithoutHint() && _hoveredHintToPaint != null &&
_hoveredHintToPaint.isOkToShow(_doc) )
{
return false;
}
}
catch (BadLocationException e)
{
return false;
}
BracketeerProcessingContainer cont = _processingThread.getBracketContainer();
List<BracketsPair> listOfPairs = cont.getMatchingPairs(origCaret, 1);
if(listOfPairs.isEmpty())
return false;
BracketsPair pair = listOfPairs.get(0);
Position pos = pair.getClosingBracket().getPosition();
if(pos == null || !pos.overlapsWith(origCaret, 2))
return false;
if( pair.getClosingBracket().getChar() != '}' )
return false;
pos = pair.getOpeningBracket().getPosition();
if( pos == null )
return false;
// this this bracket visible?
if( getInclusiveTopIndexStartOffset() < pos.getOffset() )
return false;
PaintableBracket paintBracket = null;
synchronized (_hoveredPairsToPaint)
{
for (PaintableBracket paintableBracket : _hoveredPairsToPaint)
{
if(paintableBracket.getPosition().equals(pos))
{
paintBracket = paintableBracket;
break;
}
}
}
if(paintBracket == null)
{
Activator.log(Messages.BracketsHighlighter_MatchNotHighlighetd);
return false;
}
try
{
_popup = new Popup(_sourceViewer, _textWidget, _doc, paintBracket);
}
catch(BadLocationException e)
{
_popup = null;
return false;
}
return true;
}
private boolean markHoveredBrackets(int origCaret)
{
// int startPoint = Math.max(0, origCaret - 2);
// int endPoint = Math.min(_sourceViewer.getDocument().getLength(),
// origCaret + 2);
if( !_conf.getPairConfiguration().isHoveredPairsEnabled() )
return false;
int length = 4;
int startPoint = origCaret-2;
BracketeerProcessingContainer cont = _processingThread.getBracketContainer();
List<BracketsPair> listOfPairs = cont.getMatchingPairs(startPoint, length);
listOfPairs = sortPairs(listOfPairs);
if(listOfPairs.isEmpty())
return false;
// do nothing if _hoveredPairsToPaint is equal to listOfPairs
if(areEqualPairs(listOfPairs, _hoveredPairsToPaint))
return true;
clearHoveredPairsToPaint();
synchronized (_hoveredPairsToPaint)
{
addPaintableObjectsPairs(listOfPairs, 0, 1, _hoveredPairsToPaint);
}
// TODO: optimize? (redraw only the needed sections)
_textWidget.redraw();
//drawHighlights();
return true;
}
private boolean showHoveredHint(int origCaret)
{
BracketeerProcessingContainer cont = _processingThread.getBracketContainer();
Hint hint = cont.getHint(origCaret);
HintConfiguration conf = _conf.getHintConfiguration();
if( !conf.isShowOnHover() )
hint = null;
PaintableHint hintToPaint = null;
if( hint != null )
{
String type = hint.getType();
hintToPaint = new PaintableHint(hint.getHintPositionRaw(),
conf.getColor(type, true),
conf.getColor(type, false),
conf.isItalic(type),
conf.formatTextHovered(type, hint.getTxt()));
}
boolean redraw = false;
if( _hoveredHintToPaint == null && hintToPaint != null )
redraw = true;
else if( _hoveredHintToPaint != null &&
!_hoveredHintToPaint.equals(hintToPaint) )
redraw = true;
_hoveredHintToPaint = hintToPaint;
if(redraw)
{
// TODO: optimize? (redraw only the needed sections)
_textWidget.redraw();
}
return hint != null;
}
private boolean areEqualPairs(List<BracketsPair> listOfPairs,
List<PaintableBracket> pairsToPaint)
{
if( listOfPairs.size()*2 != pairsToPaint.size() )
return false;
for (BracketsPair bracketsPair : listOfPairs)
{
for( SingleBracket bracket : bracketsPair.getBrackets() )
{
boolean found = false;
for (PaintableObject paintableObject : pairsToPaint)
{
if(paintableObject.getPosition().equals(bracket.getPositionRaw()))
{
found = true;
break;
}
}
if(!found)
return false;
}
}
return true;
}
private boolean areEqualSingle(List<SingleBracket> list,
List<PaintableBracket> singlesToPaint)
{
if( list.size() != singlesToPaint.size() )
return false;
for (SingleBracket bracket : list)
{
boolean found = false;
for (PaintableObject paintableObject : singlesToPaint)
{
if(paintableObject.getPosition().equals(bracket.getPositionRaw()))
{
found = true;
break;
}
}
if(!found)
return false;
}
return true;
}
private void addPaintableObjectsPairs(List<BracketsPair> listOfPairs,
int colorCode, int colorCodeStep,
List<PaintableBracket> paintableObjectsList)
{
for (BracketsPair bracketsPair : listOfPairs)
{
for( SingleBracket bracket : bracketsPair.getBrackets() )
{
Position pos = bracket.getPositionRaw();
RGB fg = _conf.getPairConfiguration().getColor(true, colorCode);
RGB bg = _conf.getPairConfiguration().getColor(false, colorCode);
String highlightType = _conf.getPairConfiguration().getHighlightType(colorCode);
paintableObjectsList.add(new PaintableBracket(pos, fg, bg, highlightType));
}
colorCode += colorCodeStep;
}
}
private void addPaintableObjectsSingles(List<SingleBracket> listOfSingles,
List<PaintableBracket> paintableObjectsList)
{
Map<Annotation, Position> newMap = new HashMap<Annotation, Position>();
for (SingleBracket bracket : listOfSingles)
{
Position pos = bracket.getPositionRaw();
RGB fg = _conf.getSingleBracketConfiguration().getColor(true);
RGB bg = _conf.getSingleBracketConfiguration().getColor(false);
String highlightType = _conf.getSingleBracketConfiguration().getHighlightType();
paintableObjectsList.add(new PaintableBracket(pos, fg, bg, highlightType));
if( _conf.getSingleBracketConfiguration().getAnnotate() &&
_resource != null && _annotationMap != null )
{
try
{
IMarker marker = _resource.createMarker("com.chookapp.org.bracketeer.unmatchedBracket.marker"); //$NON-NLS-1$
SimpleMarkerAnnotation ma =
new SimpleMarkerAnnotation("com.chookapp.org.bracketeer.unmatchedBracket.annotation", //$NON-NLS-1$
marker);
Position newPos = new Position(pos.getOffset());
newMap.put(ma, newPos);
}
catch (CoreException e)
{
Activator.log(e);
}
}
}
Set<Annotation> oldKeySet = _annotationMap.keySet();
if( !oldKeySet.isEmpty() || !newMap.isEmpty() )
{
_annotationModel.connect(_doc);
if (_annotationModel instanceof IAnnotationModelExtension) {
((IAnnotationModelExtension)_annotationModel).replaceAnnotations(oldKeySet.toArray(new Annotation[oldKeySet.size()]), newMap);
} else {
for (Annotation annotation : oldKeySet)
{
_annotationModel.removeAnnotation(annotation);
}
Iterator<Entry<Annotation, Position>> iter= newMap.entrySet().iterator();
while (iter.hasNext()) {
Entry<Annotation, Position> mapEntry= iter.next();
_annotationModel.addAnnotation(mapEntry.getKey(), mapEntry.getValue());
}
}
_annotationMap = newMap;
_annotationModel.disconnect(_doc);
}
}
private void clearPopup()
{
if( _popup == null )
return;
_popup.dispose();
_popup = null;
}
private boolean clearHoveredPairsToPaint()
{
synchronized (_hoveredPairsToPaint)
{
if(!_hoveredPairsToPaint.isEmpty())
{
_hoveredPairsToPaint.clear();
return true;
}
}
return false;
}
private boolean clearHoveredHint()
{
boolean ret = _hoveredHintToPaint != null;
_hoveredHintToPaint = null;
return ret;
}
private boolean clearSurroundingPairsToPaint()
{
synchronized (_surroundingPairsToPaint)
{
if(!_surroundingPairsToPaint.isEmpty())
{
_surroundingPairsToPaint.clear();
return true;
}
}
return false;
}
private boolean clearSingleBracketsToPaint()
{
synchronized (_singleBracketsToPaint)
{
if(!_singleBracketsToPaint.isEmpty())
{
_singleBracketsToPaint.clear();
return true;
}
}
return false;
}
/**
* (Copied from AnnotationPainter)
*
* Computes the model (document) region that is covered by the paint event's clipping region. If
* <code>event</code> is <code>null</code>, the model range covered by the visible editor
* area (viewport) is returned.
*
* @param event the paint event or <code>null</code> to use the entire viewport
* @param isClearing tells whether the clipping is need for clearing an annotation
* @return the model region comprised by either the paint event's clipping region or the
* viewport
* @since 3.2
*/
private IRegion computeClippingRegion(PaintEvent event)
{
if (event == null) {
// trigger a repaint of the entire viewport
int vOffset= getInclusiveTopIndexStartOffset();
if (vOffset == -1)
return null;
// http://bugs.eclipse.org/bugs/show_bug.cgi?id=17147
int vLength= getExclusiveBottomIndexEndOffset() - vOffset;
return new Region(vOffset, vLength);
}
int widgetOffset;
try {
int widgetClippingStartOffset= _textWidget.getOffsetAtLocation(new Point(0, event.y));
int firstWidgetLine= _textWidget.getLineAtOffset(widgetClippingStartOffset);
widgetOffset= _textWidget.getOffsetAtLine(firstWidgetLine);
} catch (IllegalArgumentException ex1) {
try {
int firstVisibleLine= JFaceTextUtil.getPartialTopIndex(_textWidget);
widgetOffset= _textWidget.getOffsetAtLine(firstVisibleLine);
} catch (IllegalArgumentException ex2) { // above try code might fail too
widgetOffset= 0;
}
}
int widgetEndOffset;
try {
int widgetClippingEndOffset= _textWidget.getOffsetAtLocation(new Point(0, event.y + event.height));
int lastWidgetLine= _textWidget.getLineAtOffset(widgetClippingEndOffset);
widgetEndOffset= _textWidget.getOffsetAtLine(lastWidgetLine + 1);
} catch (IllegalArgumentException ex1) {
// happens if the editor is not "full", e.g. the last line of the document is visible in the editor
try {
int lastVisibleLine= JFaceTextUtil.getPartialBottomIndex(_textWidget);
if (lastVisibleLine == _textWidget.getLineCount() - 1)
// last line
widgetEndOffset= _textWidget.getCharCount();
else
widgetEndOffset= _textWidget.getOffsetAtLine(lastVisibleLine + 1) - 1;
} catch (IllegalArgumentException ex2) { // above try code might fail too
widgetEndOffset= _textWidget.getCharCount();
}
}
IRegion clippingRegion= getModelRange(widgetOffset, widgetEndOffset - widgetOffset);
return clippingRegion;
}
/**
* Returns the document offset of the upper left corner of the source viewer's view port,
* possibly including partially visible lines.
*
* @return the document offset if the upper left corner of the view port
*/
private int getInclusiveTopIndexStartOffset()
{
if (_textWidget != null && !_textWidget.isDisposed()) {
int top= JFaceTextUtil.getPartialTopIndex(_sourceViewer);
try {
IDocument document= _sourceViewer.getDocument();
return document.getLineOffset(top);
} catch (BadLocationException x) {
}
}
return -1;
}
/**
* Returns the first invisible document offset of the lower right corner of the source viewer's view port,
* possibly including partially visible lines.
*
* @return the first invisible document offset of the lower right corner of the view port
*/
private int getExclusiveBottomIndexEndOffset()
{
if (_textWidget != null && !_textWidget.isDisposed()) {
int bottom= JFaceTextUtil.getPartialBottomIndex(_sourceViewer);
try {
IDocument document= _sourceViewer.getDocument();
if (bottom >= document.getNumberOfLines())
bottom= document.getNumberOfLines() - 1;
return document.getLineOffset(bottom) + document.getLineLength(bottom);
} catch (BadLocationException x) {
}
}
return -1;
}
/**
* Returns the model region that corresponds to the given region in the
* viewer's text widget.
*
* @param offset the offset in the viewer's widget
* @param length the length in the viewer's widget
* @return the corresponding document region
* @since 3.2
*/
private IRegion getModelRange(int offset, int length)
{
if (offset == Integer.MAX_VALUE)
return null;
if (_sourceViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5) _sourceViewer;
return extension.widgetRange2ModelRange(new Region(offset, length));
}
IRegion region= _sourceViewer.getVisibleRegion();
return new Region(region.getOffset() + offset, length);
}
private IRegion getWidgetRange(int offset, int length)
{
return TextUtils.getWidgetRange(_sourceViewer, offset, length);
}
private int getDistanceBetween(Point p1, Point p2)
{
return (int) Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
private int getCurrentCaretOffset()
{
int caret = _textWidget.getCaretOffset();
caret = ((ProjectionViewer)_sourceViewer).widgetOffset2ModelOffset(caret);
caret -= 1;
return caret;
}
}