1 /**
2  *  Copyright (c) 2017 Angelo ZERR.
3  *
4  *  This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License 2.0
6  *  which accompanies this distribution, and is available at
7  *  https://www.eclipse.org/legal/epl-2.0/
8  *
9  *  SPDX-License-Identifier: EPL-2.0
10  *
11  *  Contributors:
12  *  Angelo Zerr <angelo.zerr@gmail.com> - [CodeMining] Provide inline annotations support - Bug 527675
13  */
14 package org.eclipse.jface.text.examples.sources.inlined;
15 
16 import java.util.HashSet;
17 import java.util.Set;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20 
21 import org.eclipse.jface.text.BadLocationException;
22 import org.eclipse.jface.text.Document;
23 import org.eclipse.jface.text.IDocument;
24 import org.eclipse.jface.text.IRegion;
25 import org.eclipse.jface.text.ITextViewerExtension2;
26 import org.eclipse.jface.text.Position;
27 import org.eclipse.jface.text.reconciler.DirtyRegion;
28 import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
29 import org.eclipse.jface.text.reconciler.MonoReconciler;
30 import org.eclipse.jface.text.source.Annotation;
31 import org.eclipse.jface.text.source.AnnotationModel;
32 import org.eclipse.jface.text.source.AnnotationPainter;
33 import org.eclipse.jface.text.source.IAnnotationAccess;
34 import org.eclipse.jface.text.source.ISourceViewer;
35 import org.eclipse.jface.text.source.SourceViewer;
36 import org.eclipse.jface.text.source.inlined.AbstractInlinedAnnotation;
37 import org.eclipse.jface.text.source.inlined.InlinedAnnotationSupport;
38 import org.eclipse.jface.text.source.inlined.LineContentAnnotation;
39 import org.eclipse.jface.text.source.inlined.LineHeaderAnnotation;
40 import org.eclipse.jface.text.source.inlined.Positions;
41 import org.eclipse.swt.SWT;
42 import org.eclipse.swt.graphics.Color;
43 import org.eclipse.swt.graphics.Device;
44 import org.eclipse.swt.layout.FillLayout;
45 import org.eclipse.swt.widgets.Display;
46 import org.eclipse.swt.widgets.Shell;
47 
48 /**
49  * An inlined demo with {@link LineHeaderAnnotation} and
50  * {@link LineContentAnnotation} annotations both:
51  *
52  * <ul>
53  * <li>a status OK, NOK is displayed before the line which starts with 'color:'.
54  * This status is the result of the content after 'color' which must be a rgb
55  * content. Here {@link ColorStatusAnnotation} is used.</li>
56  * <li>a colorized square is displayed before the rgb declaration (inside the
57  * line content). Here {@link ColorAnnotation} is used.</li>
58  * </ul>
59  *
60  */
61 public class InlinedAnnotationDemo {
62 
main(String[] args)63 	public static void main(String[] args) throws Exception {
64 
65 		Display display = new Display();
66 		Shell shell = new Shell(display);
67 		shell.setLayout(new FillLayout());
68 		shell.setText("Inlined annotation demo");
69 
70 		// Create source viewer and initialize the content
71 		ISourceViewer sourceViewer = new SourceViewer(shell, null, SWT.V_SCROLL | SWT.BORDER);
72 		sourceViewer.setDocument(new Document("\ncolor:rgb(255, 255, 0)"), new AnnotationModel());
73 
74 		// Initialize inlined annotations support
75 		InlinedAnnotationSupport support = new InlinedAnnotationSupport();
76 		support.install(sourceViewer, createAnnotationPainter(sourceViewer));
77 
78 		// Refresh inlined annotation in none UI Thread with reconciler.
79 		MonoReconciler reconciler = new MonoReconciler(new IReconcilingStrategy() {
80 
81 			@Override
82 			public void setDocument(IDocument document) {
83 				Set<AbstractInlinedAnnotation> annotations = getInlinedAnnotation(sourceViewer, support);
84 				support.updateAnnotations(annotations);
85 			}
86 
87 			@Override
88 			public void reconcile(IRegion partition) {
89 				Set<AbstractInlinedAnnotation> anns = getInlinedAnnotation(sourceViewer, support);
90 				support.updateAnnotations(anns);
91 			}
92 
93 			@Override
94 			public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
95 
96 			}
97 		}, false);
98 		reconciler.setDelay(1);
99 		reconciler.install(sourceViewer);
100 
101 		shell.open();
102 		while (!shell.isDisposed()) {
103 			if (!display.readAndDispatch())
104 				display.sleep();
105 		}
106 		display.dispose();
107 	}
108 
109 	/**
110 	 * Create annotation painter.
111 	 *
112 	 * @param viewer
113 	 *            the viewer.
114 	 * @return annotation painter.
115 	 */
createAnnotationPainter(ISourceViewer viewer)116 	private static AnnotationPainter createAnnotationPainter(ISourceViewer viewer) {
117 		IAnnotationAccess annotationAccess = new IAnnotationAccess() {
118 			@Override
119 			public Object getType(Annotation annotation) {
120 				return annotation.getType();
121 			}
122 
123 			@Override
124 			public boolean isMultiLine(Annotation annotation) {
125 				return true;
126 			}
127 
128 			@Override
129 			public boolean isTemporary(Annotation annotation) {
130 				return true;
131 			}
132 
133 		};
134 		AnnotationPainter painter = new AnnotationPainter(viewer, annotationAccess);
135 		((ITextViewerExtension2) viewer).addPainter(painter);
136 		return painter;
137 	}
138 
139 	/**
140 	 * Returns the inlined annotations list to display in the given viewer.
141 	 *
142 	 * @param viewer
143 	 *            the viewer
144 	 * @param support
145 	 *            the inlined annotation suppor.
146 	 * @return the inlined annotations list to display in the given viewer.
147 	 */
getInlinedAnnotation(ISourceViewer viewer, InlinedAnnotationSupport support)148 	private static Set<AbstractInlinedAnnotation> getInlinedAnnotation(ISourceViewer viewer,
149 			InlinedAnnotationSupport support) {
150 		IDocument document = viewer.getDocument();
151 		Set<AbstractInlinedAnnotation> annotations = new HashSet<>();
152 		int lineCount = document.getNumberOfLines();
153 		for (int i = 0; i < lineCount; i++) {
154 			String line = getLineText(document, i).trim();
155 			int index = line.indexOf("color:");
156 			if (index == 0) {
157 				String rgb = line.substring(index + "color:".length(), line.length()).trim();
158 				try {
159 					String status = "OK!";
160 					Color color = parse(rgb, viewer.getTextWidget().getDisplay());
161 					if (color != null) {
162 					} else {
163 						status = "ERROR!";
164 					}
165 					// Status color annotation
166 					Position pos = Positions.of(i, document, true);
167 					ColorStatusAnnotation statusAnnotation = support.findExistingAnnotation(pos);
168 					if (statusAnnotation == null) {
169 						statusAnnotation = new ColorStatusAnnotation(pos, viewer);
170 					}
171 					statusAnnotation.setStatus(status);
172 					annotations.add(statusAnnotation);
173 
174 					// Color annotation
175 					if (color != null) {
176 						Position colorPos = new Position(pos.offset + index + "color:".length(), 1);
177 						ColorAnnotation colorAnnotation = support.findExistingAnnotation(colorPos);
178 						if (colorAnnotation == null) {
179 							colorAnnotation = new ColorAnnotation(colorPos, viewer);
180 						}
181 						colorAnnotation.setColor(color);
182 						annotations.add(colorAnnotation);
183 					}
184 
185 					// rgb parameter names annotations
186 					int rgbIndex = line.indexOf("rgb");
187 					if (rgbIndex != -1) {
188 						rgbIndex = rgbIndex + "rgb".length();
189 						int startOffset = pos.offset + rgbIndex;
190 						String rgbContent = line.substring(rgbIndex, line.length());
191 						int startIndex = addRGBParamNameAnnotation("red:", rgbContent, 0, startOffset, viewer, support,
192 								annotations);
193 						if (startIndex != -1) {
194 							startIndex = addRGBParamNameAnnotation("green:", rgbContent, startIndex, startOffset, viewer,
195 									support, annotations);
196 							if (startIndex != -1) {
197 								startIndex = addRGBParamNameAnnotation("blue:", rgbContent, startIndex, startOffset,
198 										viewer, support, annotations);
199 							}
200 						}
201 					}
202 
203 				} catch (BadLocationException e) {
204 					e.printStackTrace();
205 				}
206 			}
207 		}
208 		return annotations;
209 	}
210 
211 	/**
212 	 * Add RGB parameter name annotation
213 	 *
214 	 * @param paramName
215 	 * @param rgbContent
216 	 * @param startIndex
217 	 * @param startOffset
218 	 * @param viewer
219 	 * @param support
220 	 * @param annotations
221 	 * @return the current parsed index
222 	 */
addRGBParamNameAnnotation(String paramName, String rgbContent, int startIndex, int startOffset, ISourceViewer viewer, InlinedAnnotationSupport support, Set<AbstractInlinedAnnotation> annotations)223 	private static int addRGBParamNameAnnotation(String paramName, String rgbContent, int startIndex, int startOffset,
224 			ISourceViewer viewer, InlinedAnnotationSupport support, Set<AbstractInlinedAnnotation> annotations) {
225 		char startChar = startIndex == 0 ? '(' : ',';
226 		char[] chars = rgbContent.toCharArray();
227 		for (int i = startIndex; i < chars.length; i++) {
228 			char c = chars[i];
229 			if (c == startChar) {
230 				if (i == chars.length - 1) {
231 					return -1;
232 				}
233 				Position paramPos = new Position(startOffset + i + 1, 1);
234 				LineContentAnnotation colorParamAnnotation = support.findExistingAnnotation(paramPos);
235 				if (colorParamAnnotation == null) {
236 					colorParamAnnotation = new LineContentAnnotation(paramPos, viewer);
237 				}
238 				colorParamAnnotation.setText(paramName);
239 				annotations.add(colorParamAnnotation);
240 				return i + 1;
241 			}
242 		}
243 		return -1;
244 	}
245 
246 	/**
247 	 * Parse the given input rgb color and returns an instance of SWT Color and null
248 	 * otherwise.
249 	 *
250 	 * @param input
251 	 *            the rgb string color
252 	 * @param device
253 	 * @return the created color and null otherwise.
254 	 */
parse(String input, Device device)255 	private static Color parse(String input, Device device) {
256 		Pattern c = Pattern.compile("rgb *\\( *([0-9]+), *([0-9]+), *([0-9]+) *\\)");
257 		Matcher m = c.matcher(input);
258 		if (m.matches()) {
259 			try {
260 				return new Color(device, Integer.valueOf(m.group(1)), // r
261 						Integer.valueOf(m.group(2)), // g
262 						Integer.valueOf(m.group(3))); // b
263 			} catch (Exception e) {
264 
265 			}
266 		}
267 		return null;
268 	}
269 
270 	/**
271 	 * Returns the line text.
272 	 *
273 	 * @param document
274 	 *            the document.
275 	 * @param line
276 	 *            the line index.
277 	 * @return the line text.
278 	 */
getLineText(IDocument document, int line)279 	private static String getLineText(IDocument document, int line) {
280 		try {
281 			int offset = document.getLineOffset(line);
282 			int length = document.getLineLength(line);
283 			return document.get(offset, length);
284 		} catch (Exception e) {
285 			e.printStackTrace();
286 			return null;
287 		}
288 	}
289 }
290