Skip to main content
summaryrefslogtreecommitdiffstats
blob: be8d549897ceaa8cf411351f3ecd4ccf1478076a (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
/*******************************************************************************
 * Copyright (c) 2000, 2014 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
 *     Sergey Prigogin (Google) - bug 441448
 *******************************************************************************/

package org.eclipse.jface.text.formatter;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.TypedPosition;

/**
 * Content formatter for edit-based formatting strategies.
 * <p>
 * Two kinds of formatting strategies can be registered with this formatter:
 * <ul>
 * <li>one master formatting strategy for the default content type</li>
 * <li>one formatting strategy for each non-default content type</li>
 * </ul>
 * The master formatting strategy always formats the whole region to be
 * formatted in the first pass. In a second pass, all partitions of the region
 * to be formatted that are not of master content type are formatted using the
 * slave formatting strategy registered for the underlying content type. All
 * formatting strategies must implement {@link IFormattingStrategyExtension}.
 * <p>
 * Regions to be formatted with the master formatting strategy always have
 * an offset aligned to the line start. Regions to be formatted with slave formatting
 * strategies are aligned on partition boundaries.
 *
 * @see IFormattingStrategyExtension
 * @since 3.0
 */
public class MultiPassContentFormatter implements IContentFormatter, IContentFormatterExtension {

	/**
	 * Position updater that shifts otherwise deleted positions to the next
	 * non-whitespace character. The length of the positions are truncated to
	 * one if the position was shifted.
	 */
	protected class NonDeletingPositionUpdater extends DefaultPositionUpdater {

		/**
		 * Creates a new non-deleting position updater.
		 *
		 * @param category The position category to update its positions
		 */
		public NonDeletingPositionUpdater(final String category) {
			super(category);
		}

		@Override
		protected final boolean notDeleted() {

			if (fOffset < fPosition.offset && (fPosition.offset + fPosition.length < fOffset + fLength)) {

				int offset= fOffset + fLength;
				if (offset < fDocument.getLength()) {

					try {

						boolean moved= false;
						char character= fDocument.getChar(offset);

						while (offset < fDocument.getLength() && Character.isWhitespace(character)) {

							moved= true;
							character= fDocument.getChar(offset++);
						}

						if (moved)
							offset--;

					} catch (BadLocationException exception) {
						// Can not happen
					}

					fPosition.offset= offset;
					fPosition.length= 0;
				}
			}
			return true;
		}
	}

	/** The master formatting strategy */
	private IFormattingStrategyExtension fMaster= null;
	/** The partitioning of this content formatter */
	private final String fPartitioning;
	/** The slave formatting strategies */
	private final Map fSlaves= new HashMap();
	/** The default content type */
	private final String fType;

	/**
	 * Creates a new content formatter.
	 *
	 * @param partitioning the document partitioning for this formatter
	 * @param type the default content type
	 */
	public MultiPassContentFormatter(final String partitioning, final String type) {
		fPartitioning= partitioning;
		fType= type;
	}

	@Override
	public final void format(final IDocument medium, final IFormattingContext context) {

		context.setProperty(FormattingContextProperties.CONTEXT_MEDIUM, medium);

		final Boolean document= (Boolean)context.getProperty(FormattingContextProperties.CONTEXT_DOCUMENT);
		if (document == null || !document.booleanValue()) {

			final IRegion region= (IRegion)context.getProperty(FormattingContextProperties.CONTEXT_REGION);
			if (region != null) {
				try {
					formatMaster(context, medium, region.getOffset(), region.getLength());
				} finally {
					formatSlaves(context, medium, region.getOffset(), region.getLength());
				}
			}
		} else {
			try {
				formatMaster(context, medium, 0, medium.getLength());
			} finally {
				formatSlaves(context, medium, 0, medium.getLength());
			}
		}
	}

	@Override
	public final void format(final IDocument medium, final IRegion region) {

		final FormattingContext context= new FormattingContext();

		context.setProperty(FormattingContextProperties.CONTEXT_DOCUMENT, Boolean.FALSE);
		context.setProperty(FormattingContextProperties.CONTEXT_REGION, region);

		format(medium, context);
	}

	/**
	 * Formats the document specified in the formatting context with the master
	 * formatting strategy.
	 * <p>
	 * The master formatting strategy covers all regions of the document. The
	 * offset of the region to be formatted is aligned on line start boundaries,
	 * whereas the end index of the region remains the same. For this formatting
	 * type the document partitioning is not taken into account.
	 *
	 * @param context The formatting context to use
	 * @param document The document to operate on
	 * @param offset The offset of the region to format
	 * @param length The length of the region to format
	 */
	protected void formatMaster(final IFormattingContext context, final IDocument document, int offset, int length) {

		try {

			if (length != 0) {
				// Extend the selection to the beginning of line if it is not empty.
				// An empty selection must remain empty since it may be treated in
				// a special way by the formatter.
				final int delta= offset - document.getLineInformationOfOffset(offset).getOffset();
				offset -= delta;
				length += delta;
			}

		} catch (BadLocationException exception) {
			// Do nothing
		}

		if (fMaster != null) {

			context.setProperty(FormattingContextProperties.CONTEXT_PARTITION, new TypedPosition(offset, length, fType));

			fMaster.formatterStarts(context);
			fMaster.format();
			fMaster.formatterStops();
		}
	}

	/**
	 * Formats the document specified in the formatting context with the
	 * formatting strategy registered for the content type.
	 * <p>
	 * For this formatting type only slave strategies are used. The region to be
	 * formatted is aligned on partition boundaries of the underlying content
	 * type. The exact formatting strategy is determined by the underlying
	 * content type of the document partitioning.
	 *
	 * @param context The formatting context to use
	 * @param document The document to operate on
	 * @param offset The offset of the region to format
	 * @param length The length of the region to format
	 * @param type The content type of the region to format
	 */
	protected void formatSlave(final IFormattingContext context, final IDocument document, final int offset, final int length, final String type) {

		final IFormattingStrategyExtension strategy= (IFormattingStrategyExtension)fSlaves.get(type);
		if (strategy != null) {

			context.setProperty(FormattingContextProperties.CONTEXT_PARTITION, new TypedPosition(offset, length, type));

			strategy.formatterStarts(context);
			strategy.format();
			strategy.formatterStops();
		}
	}

	/**
	 * Formats the document specified in the formatting context with the slave
	 * formatting strategies.
	 * <p>
	 * For each content type of the region to be formatted in the document
	 * partitioning, the registered slave formatting strategy is used to format
	 * that particular region. The region to be formatted is aligned on
	 * partition boundaries of the underlying content type. If the content type
	 * is the document's default content type, nothing happens.
	 *
	 * @param context The formatting context to use
	 * @param document The document to operate on
	 * @param offset The offset of the region to format
	 * @param length The length of the region to format
	 */
	protected void formatSlaves(final IFormattingContext context, final IDocument document, final int offset, final int length) {

		Map partitioners= new HashMap(0);
		try {

			final ITypedRegion[] partitions= TextUtilities.computePartitioning(document, fPartitioning, offset, length, false);

			if (!fType.equals(partitions[0].getType()))
				partitions[0]= TextUtilities.getPartition(document, fPartitioning, partitions[0].getOffset(), false);

			if (partitions.length > 1) {

				if (!fType.equals(partitions[partitions.length - 1].getType()))
					partitions[partitions.length - 1]= TextUtilities.getPartition(document, fPartitioning, partitions[partitions.length - 1].getOffset(), false);
			}

			String type= null;
			ITypedRegion partition= null;

			partitioners= TextUtilities.removeDocumentPartitioners(document);

			for (int index= partitions.length - 1; index >= 0; index--) {

				partition= partitions[index];
				type= partition.getType();

				if (!fType.equals(type))
					formatSlave(context, document, partition.getOffset(), partition.getLength(), type);
			}

		} catch (BadLocationException exception) {
			// Should not happen
		} finally {
			TextUtilities.addDocumentPartitioners(document, partitioners);
		}
	}

	@Override
	public final IFormattingStrategy getFormattingStrategy(final String type) {
		return null;
	}

	/**
	 * Registers a master formatting strategy.
	 * <p>
	 * The strategy may already be registered with a certain content type as
	 * slave strategy. The master strategy is registered for the default content
	 * type of documents. If a master strategy has already been registered, it
	 * is overridden by the new one.
	 *
	 * @param strategy The master formatting strategy, must implement
	 * 	{@link IFormattingStrategyExtension}
	 */
	public final void setMasterStrategy(final IFormattingStrategy strategy) {
		Assert.isTrue(strategy instanceof IFormattingStrategyExtension);
		fMaster= (IFormattingStrategyExtension) strategy;
	}

	/**
	 * Registers a slave formatting strategy for a certain content type.
	 * <p>
	 * The strategy may already be registered as master strategy. An
	 * already registered slave strategy for the specified content type
	 * will be replaced. However, the same strategy may be registered with
	 * several content types. Slave strategies cannot be registered for the
	 * default content type of documents.
	 *
	 * @param strategy The slave formatting strategy
	 * @param type The content type to register this strategy with,
	 * 	must implement {@link IFormattingStrategyExtension}
	 */
	public final void setSlaveStrategy(final IFormattingStrategy strategy, final String type) {
		Assert.isTrue(strategy instanceof IFormattingStrategyExtension);
		if (!fType.equals(type))
			fSlaves.put(type, strategy);
	}
}

Back to the top