| /******************************************************************************* |
| * 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; |
| } |
| } |