Skip to main content
summaryrefslogtreecommitdiffstats
blob: 4b1bc516477d47e2ed9949fbfb55210e95de68fb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.text.source;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;

import org.eclipse.jface.action.ToolBarManager;

import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.information.IInformationProviderExtension2;


/**
 * A hover for line oriented diffs. It determines the text to show as hover for a certain line in the
 * document.
 *
 * @since 3.0
 */
public class LineChangeHover implements IAnnotationHover, IAnnotationHoverExtension, IInformationProviderExtension2 {

	@Override
	public String getHoverInfo(ISourceViewer sourceViewer, int lineNumber) {
		return null;
	}

	/**
	 * Formats the source w/ syntax coloring etc. This implementation replaces tabs with spaces.
	 * May be overridden by subclasses.
	 *
	 * @param content the hover content
	 * @return <code>content</code> reformatted
	 */
	protected String formatSource(String content) {
		if (content != null) {
			StringBuffer sb= new StringBuffer(content);
			final String tabReplacement= getTabReplacement();
			for (int pos= 0; pos < sb.length(); pos++) {
				if (sb.charAt(pos) == '\t')
					sb.replace(pos, pos + 1, tabReplacement);
			}
			return sb.toString();
		}
		return content;
	}

	/**
	 * Returns a replacement for the tab character. The default implementation
	 * returns a tabulator character, but subclasses may override to specify a
	 * number of spaces.
	 *
	 * @return a whitespace String that will be substituted for the tabulator
	 *         character
	 */
	protected String getTabReplacement() {
		return "\t"; //$NON-NLS-1$
	}

	/**
	 * Computes the content of the hover for the document contained in <code>viewer</code> on
	 * line <code>line</code>.
	 *
	 * @param viewer the connected viewer
	 * @param first the first line in <code>viewer</code>'s document to consider
	 * @param last the last line in <code>viewer</code>'s document to consider
	 * @param maxLines the max number of lines
	 * @return The hover content corresponding to the parameters
	 * @see #getHoverInfo(ISourceViewer, int)
	 * @see #getHoverInfo(ISourceViewer, ILineRange, int)
	 */
	private String computeContent(ISourceViewer viewer, int first, int last, int maxLines) {
		ILineDiffer differ= getDiffer(viewer);
		if (differ == null)
			return null;

		final List<ILineDiffInfo> lines= new LinkedList<>();
		for (int l= first; l <= last; l++) {
			ILineDiffInfo info= differ.getLineInfo(l);
			if (info != null)
				lines.add(info);
		}

		return decorateText(lines, maxLines);
	}

	/**
	 * Takes a list of <code>ILineDiffInfo</code>s and computes a hover of at most <code>maxLines</code>.
	 * Added lines are prefixed with a <code>'+'</code>, changed lines with <code>'>'</code> and
	 * deleted lines with <code>'-'</code>.
	 * <p>Deleted and added lines can even each other out, so that a number of deleted lines get
	 * displayed where - in the current document - the added lines are.
	 *
	 * @param diffInfos a <code>List</code> of <code>ILineDiffInfo</code>
	 * @param maxLines the maximum number of lines. Note that adding up all annotations might give
	 * more than that due to deleted lines.
	 * @return a <code>String</code> suitable for hover display
	 */
	protected String decorateText(List<? extends ILineDiffInfo> diffInfos, int maxLines) {
		/* maxLines controls the size of the hover (not more than what fits into the display are of
		 * the viewer).
		 * added controls how many lines are added - added lines are
		 */
		StringBuffer text = new StringBuffer();
		int added= 0;
		for (Iterator<? extends ILineDiffInfo> it= diffInfos.iterator(); it.hasNext();) {
			ILineDiffInfo info= it.next();
			String[] original= info.getOriginalText();
			int type= info.getChangeType();
			int i= 0;
			if (type == ILineDiffInfo.ADDED)
				added++;
			else if (type == ILineDiffInfo.CHANGED) {
				text.append("> ").append(original.length > 0 ? original[i++] : ""); //$NON-NLS-1$ //$NON-NLS-2$
				maxLines--;
			} else if (type == ILineDiffInfo.UNCHANGED) {
				maxLines++;
			}
			if (maxLines == 0)
				return trimTrailing(text.toString());
			for (; i < original.length; i++) {
				text.append("- ").append( original[i]); //$NON-NLS-1$
				added--;
				if (--maxLines == 0)
					return trimTrailing(text.toString());
			}
		}
		
		text = new StringBuffer(text.toString().trim());
		if (text.length() == 0 && added-- > 0 && maxLines-- > 0) {
			text.append("+ ");//$NON-NLS-1$
		}
		while (added-- > 0 && maxLines-- > 0) {
			text.append("\n+ "); //$NON-NLS-1$
		}
		return text.toString();
	}

	/**
	 * Trims trailing spaces
	 *
	 * @param text a <code>String</code>
	 * @return a copy of <code>text</code> with trailing spaces removed
	 */
	private String trimTrailing(String text) {
		int pos= text.length() - 1;
		while (pos >= 0 && Character.isWhitespace(text.charAt(pos))) {
			pos--;
		}
		return text.substring(0, pos + 1);
	}

	/**
	 * Extracts the line differ - if any - from the viewer's document's annotation model.
	 * @param viewer the viewer
	 * @return a line differ for the document displayed in viewer, or <code>null</code>.
	 */
	private ILineDiffer getDiffer(ISourceViewer viewer) {
		IAnnotationModel model= viewer.getAnnotationModel();

		if (model == null)
			return null;

		if (model instanceof IAnnotationModelExtension) {
			IAnnotationModel diffModel= ((IAnnotationModelExtension)model).getAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID);
			if (diffModel != null)
				model= diffModel;
		}
		if (model instanceof ILineDiffer) {
			if (model instanceof ILineDifferExtension2 && ((ILineDifferExtension2)model).isSuspended())
				return null;
			return (ILineDiffer)model;
		}
		return null;
	}

	/**
	 * Computes the block of lines which form a contiguous block of changes covering <code>line</code>.
	 *
	 * @param viewer the source viewer showing
	 * @param line the line which a hover is displayed for
	 * @param min the first line in <code>viewer</code>'s document to consider
	 * @param max the last line in <code>viewer</code>'s document to consider
	 * @return the selection in the document displayed in <code>viewer</code> containing <code>line</code>
	 * that is covered by the hover information returned by the receiver.
	 */
	protected Point computeLineRange(ISourceViewer viewer, int line, int min, int max) {
		/* Algorithm:
		 * All lines that have changes to themselves (added, changed) are taken that form a
		 * contiguous block of lines that includes <code>line</code>.
		 *
		 * If <code>line</code> is itself unchanged, if there is a deleted line either above or
		 * below, or both, the lines +/- 1 from <code>line</code> are included in the search as well,
		 * without applying this last rule to them, though. (I.e., if <code>line</code> is unchanged,
		 * but has a deleted line above, this one is taken in. If the line above has changes, the block
		 * is extended from there. If the line has no changes itself, the search stops).
		 *
		 * The block never extends the visible line range of the viewer.
		 */

		ILineDiffer differ= getDiffer(viewer);
		if (differ == null)
			return new Point(-1, -1);

		// backward search

		int l= line;
		ILineDiffInfo info= differ.getLineInfo(l);
		// search backwards until a line has no changes to itself
		while (l >= min && info != null && (info.getChangeType() == ILineDiffInfo.CHANGED || info.getChangeType() == ILineDiffInfo.ADDED)) {
			info= differ.getLineInfo(--l);
		}

		int first= Math.min(l + 1, line);

		// forward search

		l= line;
		info= differ.getLineInfo(l);
		// search forward until a line has no changes to itself
		while (l <= max && info != null && (info.getChangeType() == ILineDiffInfo.CHANGED || info.getChangeType() == ILineDiffInfo.ADDED)) {
			info= differ.getLineInfo(++l);
		}

		int last= Math.max(l - 1, line);

		return new Point(first, last);
	}

	@Override
	public Object getHoverInfo(ISourceViewer sourceViewer, ILineRange lineRange, int visibleLines) {
		int first= adaptFirstLine(sourceViewer, lineRange.getStartLine());
		int last= adaptLastLine(sourceViewer, lineRange.getStartLine() + lineRange.getNumberOfLines() - 1);
		String content= computeContent(sourceViewer, first, last, visibleLines);
		return formatSource(content);
	}

	/**
	 * Adapts the start line to the implementation of <code>ILineDiffInfo</code>.
	 *
	 * @param viewer the source viewer
	 * @param startLine the line to adapt
	 * @return <code>startLine - 1</code> if that line exists and is an
	 *         unchanged line followed by deletions, <code>startLine</code>
	 *         otherwise
	 */
	private int adaptFirstLine(ISourceViewer viewer, int startLine) {
		ILineDiffer differ= getDiffer(viewer);
		if (differ != null && startLine > 0) {
			int l= startLine - 1;
			ILineDiffInfo info= differ.getLineInfo(l);
			if (info != null && info.getChangeType() == ILineDiffInfo.UNCHANGED && info.getRemovedLinesBelow() > 0)
				return l;
		}
		return startLine;
	}

	/**
	 * Adapts the last line to the implementation of <code>ILineDiffInfo</code>.
	 *
	 * @param viewer the source viewer
	 * @param lastLine the line to adapt
	 * @return <code>lastLine - 1</code> if that line exists and is an
	 *         unchanged line followed by deletions, <code>startLine</code>
	 *         otherwise
	 */
	private int adaptLastLine(ISourceViewer viewer, int lastLine) {
		ILineDiffer differ= getDiffer(viewer);
		if (differ != null && lastLine > 0) {
			ILineDiffInfo info= differ.getLineInfo(lastLine);
			if (info != null && info.getChangeType() == ILineDiffInfo.UNCHANGED)
				return lastLine - 1;
		}
		return lastLine;
	}

	@Override
	public ILineRange getHoverLineRange(ISourceViewer viewer, int lineNumber) {
		IDocument document= viewer.getDocument();
		if (document != null) {
			Point range= computeLineRange(viewer, lineNumber, 0, Math.max(0, document.getNumberOfLines() - 1));
			if (range.x != -1 && range.y != -1)
				return new LineRange(range.x, range.y - range.x + 1);
		}
		return null;
	}

	@Override
	public boolean canHandleMouseCursor() {
		return false;
	}

	@Override
	public IInformationControlCreator getHoverControlCreator() {
		return null;
	}

	@Override
	public IInformationControlCreator getInformationPresenterControlCreator() {
		return new IInformationControlCreator() {
			@Override
			public IInformationControl createInformationControl(Shell parent) {
				return new DefaultInformationControl(parent, (ToolBarManager)null, null);
			}
		};
	}
}

Back to the top