blob: d0cb79141c3d89cd46c3f63a98e349119afe3298 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 Red Hat Inc. and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* - Mickael Istria (Red Hat Inc.) - initial implementation
*******************************************************************************/
package org.eclipse.jface.text.tests.codemining;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.util.Util;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.codemining.ICodeMining;
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.MonoReconciler;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.AnnotationPainter;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.tests.ScreenshotOnFailureRule;
import org.eclipse.jface.text.tests.TextViewerTest;
import org.eclipse.jface.text.tests.util.DisplayHelper;
public class CodeMiningTest {
private SourceViewer fViewer;
private Shell fShell;
@Rule public ScreenshotOnFailureRule rule = new ScreenshotOnFailureRule();
@Before
public void setUp() {
fShell= new Shell(Display.getDefault());
fShell.setSize(500, 200);
fShell.setLayout(new FillLayout());
fViewer= new SourceViewer(fShell, null, SWT.NONE);
final StyledText textWidget= fViewer.getTextWidget();
textWidget.setText("a");
textWidget.setText("");
MonoReconciler reconciler = new MonoReconciler(new IReconcilingStrategy() {
@Override
public void setDocument(IDocument document) {
fViewer.updateCodeMinings();
}
@Override
public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
// nothing to do
}
@Override
public void reconcile(IRegion partition) {
fViewer.updateCodeMinings();
}
}, false);
reconciler.install(fViewer);
fViewer.setDocument(new Document(), new AnnotationModel());
fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { new DelayedEchoCodeMiningProvider() });
AnnotationPainter annotationPainter = new AnnotationPainter(fViewer, null);
fViewer.setCodeMiningAnnotationPainter(annotationPainter);
fViewer.addPainter(annotationPainter);
// this currently needs to be repeated
fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { new DelayedEchoCodeMiningProvider() });
final Display display = textWidget.getDisplay();
fShell.open();
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return fViewer.getTextWidget().isVisible();
}
}.waitForCondition(display, 3000));
DisplayHelper.sleep(textWidget.getDisplay(), 1000);
}
@After
public void tearDown() {
fShell.dispose();
fViewer = null;
}
@Test
public void testCodeMiningFirstLine() {
fViewer.getDocument().set("echo");
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return fViewer.getTextWidget().getLineVerticalIndent(0) > 0;
}
}.waitForCondition(fViewer.getControl().getDisplay(), 3000));
}
@Test
public void testCodeMiningCtrlHome() throws BadLocationException {
Assume.assumeFalse("See bug 541415. For whatever reason, this shortcut doesn't work on Mac", Util.isMac());
DelayedEchoCodeMiningProvider.DELAY = 500;
fViewer.getDocument().set(TextViewerTest.generate5000Lines());
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return fViewer.getTextWidget().getText().length() > 5000;
}
}.waitForCondition(fViewer.getControl().getDisplay(), 3000));
TextViewerTest.ctrlEnd(fViewer);
final int lastLine = fViewer.getDocument().getNumberOfLines() - 1;
final int lastLineOffset = fViewer.getDocument().getLineOffset(lastLine);
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return lastLineOffset >= fViewer.getVisibleRegion().getOffset() && lastLineOffset <= fViewer.getVisibleRegion().getOffset() + fViewer.getVisibleRegion().getLength();
}
}.waitForCondition(fViewer.getControl().getDisplay(), 3000));
DisplayHelper.sleep(fViewer.getControl().getDisplay(), 500);
AtomicInteger events = new AtomicInteger();
fViewer.addViewportListener(offset ->
events.incrementAndGet());
TextViewerTest.ctrlHome(fViewer);
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return events.get() > 0;
}
}.waitForCondition(fViewer.getControl().getDisplay(), 3000));
Assert.assertEquals(0, fViewer.getVisibleRegion().getOffset());
// wait for codemining to style line
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return fViewer.getTextWidget().getLineVerticalIndent(0) > 0;
}
}.waitForCondition(fViewer.getControl().getDisplay(), 300000));
}
@Test
public void testCodeMiningCtrlEnd() throws BadLocationException {
Assume.assumeFalse("See bug 541415. For whatever reason, this shortcut doesn't work on Mac", Util.isMac());
fViewer.getDocument().set(TextViewerTest.generate5000Lines());
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return fViewer.getTextWidget().getText().length() > 5000 && fViewer.getTextWidget().getLineVerticalIndent(0) > 0;
}
}.waitForCondition(fViewer.getControl().getDisplay(), 3000));
DisplayHelper.sleep(fViewer.getTextWidget().getDisplay(), 500);
TextViewerTest.ctrlEnd(fViewer);
final int lastLine = fViewer.getDocument().getNumberOfLines() - 1;
final int lastLineOffset = fViewer.getDocument().getLineOffset(lastLine);
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return lastLineOffset >= fViewer.getVisibleRegion().getOffset() && lastLineOffset <= fViewer.getVisibleRegion().getOffset() + fViewer.getVisibleRegion().getLength();
}
}.waitForCondition(fViewer.getControl().getDisplay(), 3000));
Assert.assertTrue(new DisplayHelper() {
@Override
protected boolean condition() {
return fViewer.getTextWidget().getLineVerticalIndent(lastLine) > 0;
}
}.waitForCondition(fViewer.getControl().getDisplay(), 3000));
}
@Test
public void testCodeMiningMultiLine() throws BadLocationException {
fViewer.getDocument().set("a\nbc");
fViewer.setCodeMiningProviders(new ICodeMiningProvider[] { new ICodeMiningProvider() {
@Override
public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer, IProgressMonitor monitor) {
return CompletableFuture.completedFuture(Collections.singletonList(new StaticContentLineCodeMining(new Position(0, 3), "long enough code mining to be wider than actual text", this)));
}
@Override
public void dispose() {
}
} });
StyledText widget = fViewer.getTextWidget();
Assert.assertFalse("Code mining is visible on 2nd line", new DisplayHelper() {
@Override
protected boolean condition() {
try {
return widget.getStyleRangeAtOffset(0) != null && widget.getStyleRangeAtOffset(0).metrics != null
&& hasCodeMiningPrintedAfterTextOnLine(fViewer, 1);
} catch (BadLocationException e) {
e.printStackTrace();
return true;
}
}
}.waitForCondition(fViewer.getTextWidget().getDisplay(), 1000));
}
private static boolean hasCodeMiningPrintedAfterTextOnLine(ITextViewer viewer, int line) throws BadLocationException {
StyledText widget = viewer.getTextWidget();
IDocument document= viewer.getDocument();
Rectangle secondLineBounds = widget.getTextBounds(document.getLineOffset(1), document.getLineOffset(line) + document.getLineLength(line) - 1);
Image image = new Image(widget.getDisplay(), widget.getSize().x, widget.getSize().y);
GC gc = new GC(widget);
gc.copyArea(image, 0, 0);
gc.dispose();
ImageData imageData = image.getImageData();
secondLineBounds.x += secondLineBounds.width; // look only area after text
for (int x = secondLineBounds.x + 1; x < image.getBounds().width && x < imageData.width; x++) {
for (int y = secondLineBounds.y; y < secondLineBounds.y + secondLineBounds.height && y < imageData.height; y++) {
if (!imageData.palette.getRGB(imageData.getPixel(x, y)).equals(widget.getBackground().getRGB())) {
// code mining printed
image.dispose();
return true;
}
}
}
image.dispose();
return false;
}
}