2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.editor.structure.formatting;
44
import java.io.IOException;
45
import java.io.Writer;
46
import java.util.ArrayList;
47
import java.util.Arrays;
48
import java.util.LinkedList;
49
import java.util.List;
50
import javax.swing.text.BadLocationException;
51
import javax.swing.text.JTextComponent;
52
import javax.swing.text.Position;
53
import org.netbeans.editor.BaseDocument;
54
import org.netbeans.editor.TokenItem;
55
import org.netbeans.editor.Utilities;
56
import org.netbeans.editor.ext.ExtFormatter;
57
import org.netbeans.editor.ext.ExtSyntaxSupport;
58
import org.openide.ErrorManager;
62
* @author Tomasz.Slota@Sun.COM
64
@Deprecated // use TagBasedLexerFormatter instead
65
public abstract class TagBasedFormatter extends ExtFormatter {
67
/** Creates a new instance of TagBases */
68
public TagBasedFormatter(Class kitClass) {
70
ErrorManager.getDefault().log(ErrorManager.WARNING,
71
"Class " + getClass().getName() + " is deprecated, use *IndentTask");
74
protected abstract ExtSyntaxSupport getSyntaxSupport(BaseDocument doc);
75
protected abstract boolean isClosingTag(TokenItem token);
76
protected abstract boolean isUnformattableToken(TokenItem token);
77
protected abstract boolean isUnformattableTag(String tag);
78
protected abstract boolean isOpeningTag(TokenItem token);
79
protected abstract String extractTagName(TokenItem tknTag);
80
protected abstract boolean areTagNamesEqual(String tagName1, String tagName2);
81
protected abstract boolean isClosingTagRequired(BaseDocument doc, String tagName);
82
protected abstract int getOpeningSymbolOffset(TokenItem tknTag);
83
protected abstract TokenItem getTagTokenEndingAtPosition(BaseDocument doc, int position) throws BadLocationException;
84
protected abstract int getTagEndOffset(TokenItem token);
86
protected Writer extFormatterReformat(final BaseDocument doc, final int startOffset, final int endOffset,
87
final boolean indentOnly) throws BadLocationException, IOException {
88
return super.reformat(doc, startOffset, endOffset, indentOnly);
91
protected boolean isWSTag(TokenItem tag){
92
char chars[] = tag.getImage().toCharArray();
95
if (!Character.isWhitespace(c)){
103
protected int getIndentForTagParameter(BaseDocument doc, TokenItem tag) throws BadLocationException{
104
int tagStartLine = Utilities.getLineOffset(doc, tag.getOffset());
105
TokenItem currentToken = tag.getNext();
108
* Find the offset of the first attribute if it is specified on the same line as the opening of the tag
112
while (currentToken != null && isWSTag(currentToken) && tagStartLine == Utilities.getLineOffset(doc, currentToken.getOffset())){
113
currentToken = currentToken.getNext();
116
if (tag != null && !isWSTag(currentToken) && tagStartLine == Utilities.getLineOffset(doc, currentToken.getOffset())){
117
return currentToken.getOffset() - Utilities.getRowIndent(doc, currentToken.getOffset()) - Utilities.getRowStart(doc, currentToken.getOffset());
120
return getShiftWidth(); // default;
123
@Override public Writer reformat(BaseDocument doc, int startOffset, int endOffset,
124
boolean indentOnly) throws BadLocationException {
126
if (!hasValidSyntaxSupport(doc)){
130
LinkedList<TagIndentationData>unprocessedOpeningTags = new LinkedList<TagIndentationData>();
131
List<TagIndentationData>matchedOpeningTags = new ArrayList<TagIndentationData>();
135
int lastLine = Utilities.getLineOffset(doc, doc.getLength());
136
int firstRefBlockLine = Utilities.getLineOffset(doc, startOffset);
137
int lastRefBlockLine = Utilities.getLineOffset(doc, endOffset);
138
int firstUnformattableLine = -1;
140
boolean unformattableLines[] = new boolean[lastLine + 1];
141
int indentsWithinTags[] = new int[lastLine + 1];
143
ExtSyntaxSupport sup = getSyntaxSupport(doc);
144
TokenItem token = sup.getTokenChain(0, doc.getLength() - 1);
147
// calc line indents - pass 1
149
boolean isOpenTag = isOpeningTag(token);
150
boolean isCloseTag = isClosingTag(token);
152
if (isOpenTag || isCloseTag){
154
String tagName = extractTagName(token);
155
int tagEndOffset = getTagEndOffset(token);
157
if (tagEndOffset == -1){
158
break; // incomplete closing tag
161
int lastTagLine = Utilities.getLineOffset(doc, tagEndOffset);
165
TagIndentationData tagData = new TagIndentationData(tagName, lastTagLine);
166
unprocessedOpeningTags.add(tagData);
168
// format lines within tag
169
int firstTagLine = Utilities.getLineOffset(doc, token.getOffset());
171
if (firstTagLine < lastTagLine){ // performance!
172
int indentWithinTag = getIndentForTagParameter(doc, token);
174
for (int i = firstTagLine + 1; i <= lastTagLine; i ++){
175
indentsWithinTags[i] = indentWithinTag;
178
// if there is only the closing symbol on the last line of tag do not indent it
179
TokenItem currentToken = token.getNext();
180
while (Utilities.getLineOffset(doc, currentToken.getOffset()) < lastTagLine
181
|| isWSTag(currentToken)){
183
currentToken = currentToken.getNext();
186
if (currentToken.getOffset() == tagEndOffset){
187
indentsWithinTags[lastTagLine] = 0;
191
// isCloseTag - find matching opening tag record
192
LinkedList<TagIndentationData>tagsToBeRemoved = new LinkedList<TagIndentationData>();
194
while (!unprocessedOpeningTags.isEmpty()){
195
TagIndentationData processedTD = unprocessedOpeningTags.removeLast();
197
if (areTagNamesEqual(tagName, processedTD.getTagName())){
198
processedTD.setClosedOnLine(lastTagLine);
199
matchedOpeningTags.add(processedTD);
201
// mark all the stuff between unformattable tag as unformattable
202
if (isUnformattableTag(tagName)){
203
for (int i = lastTagLine - 1; i > processedTD.getLine(); i --){
204
unformattableLines[i] = true;
208
// forgetting preceding tags permanently
209
tagsToBeRemoved.clear();
212
tagsToBeRemoved.add(processedTD);
216
// if matching opening tag was not found on the stack put all the tags back
217
unprocessedOpeningTags.addAll(tagsToBeRemoved);
221
boolean wasPreviousTokenUnformattable = isUnformattableToken(token);
223
if (wasPreviousTokenUnformattable && firstUnformattableLine == -1){
224
firstUnformattableLine = Utilities.getLineOffset(doc, token.getOffset());
227
token = token.getNext();
229
// detect an end of unformattable block; mark it
230
if (firstUnformattableLine > -1
231
&& (!wasPreviousTokenUnformattable || token == null)){
233
int lastUnformattableLine = token == null ? lastLine :
234
Utilities.getLineOffset(doc, token.getOffset() - 1);
236
for (int i = firstUnformattableLine + 1; i < lastUnformattableLine; i ++){
237
unformattableLines[i] = true;
240
firstUnformattableLine = -1;
243
while (token != null);
246
// calc line indents - pass 2
248
int indentLevels[] = new int[lastLine + 1];
249
Arrays.fill(indentLevels, 0);
251
for (TagIndentationData td : matchedOpeningTags){
252
// increase indent from one line after the opening tag
253
// up to one line before the closing tag
255
for (int i = td.getLine() + 1; i <= td.getClosedOnLine() - 1; i ++){
260
// when reformatting only a part of file
261
// we need to take into account the local bias
262
InitialIndentData initialIndentData = new InitialIndentData(doc, indentLevels,
263
indentsWithinTags, firstRefBlockLine, lastRefBlockLine);
265
// apply line indents
266
for (int line = firstRefBlockLine; line <= lastRefBlockLine; line ++){
267
int lineStart = Utilities.getRowStartFromLineOffset(doc, line);
269
if (!unformattableLines[line] && initialIndentData.isEligibleToIndent(line)){
270
changeRowIndent(doc, lineStart, initialIndentData.getIndent(line));
280
protected void enterPressed(JTextComponent txtComponent, int dotPos) throws BadLocationException {
281
BaseDocument doc = Utilities.getDocument(txtComponent);
282
int lineNumber = Utilities.getLineOffset(doc, dotPos);
283
int initialIndent = getInitialIndentFromPreviousLine(doc, lineNumber);
284
int endOfPreviousLine = Utilities.getFirstNonWhiteBwd(doc, dotPos);
285
endOfPreviousLine = endOfPreviousLine == -1 ? 0 : endOfPreviousLine;
287
// workaround for \n passed from code completion to reformatter
288
if (lineNumber == Utilities.getLineOffset(doc, endOfPreviousLine)){
292
TokenItem tknOpeningTag = getTagTokenEndingAtPosition(doc, endOfPreviousLine);
294
if (isOpeningTag(tknOpeningTag)){
295
TokenItem tknClosingTag = getNextClosingTag(doc, dotPos + 1);
297
if (tknClosingTag != null){
298
TokenItem tknMatchingOpeningTag = getMatchingOpeningTag(tknClosingTag);
300
if (tknMatchingOpeningTag != null
301
&& tknMatchingOpeningTag.getOffset() == tknOpeningTag.getOffset()){
303
int openingTagLine = Utilities.getLineOffset(doc, tknOpeningTag.getOffset());
304
int closingTagLine = Utilities.getLineOffset(doc, tknClosingTag.getOffset());
306
if (closingTagLine == Utilities.getLineOffset(doc, dotPos)){
308
if (openingTagLine == closingTagLine - 1){
310
* <t>|optional text</t>
312
Position closingTagPos = doc.createPosition(getOpeningSymbolOffset(tknClosingTag));
313
changeRowIndent(doc, dotPos, initialIndent + doc.getShiftWidth());
314
doc.insertString(closingTagPos.getOffset(), "\n", null); //NOI18N
315
int newCaretPos = closingTagPos.getOffset() - 1;
316
changeRowIndent(doc, closingTagPos.getOffset() + 1, initialIndent);
317
newCaretPos = Utilities.getRowEnd(doc, newCaretPos);
318
txtComponent.setCaretPosition(newCaretPos);
324
changeRowIndent(doc, dotPos, initialIndent);
329
int indent = initialIndent;
331
if (isClosingTagRequired(doc, extractTagName(tknOpeningTag))){
332
indent += doc.getShiftWidth();
335
changeRowIndent(doc, dotPos, indent);
338
int indent = initialIndent;
340
if (isJustBeforeClosingTag(doc, dotPos)){
341
indent -= doc.getShiftWidth();
342
indent = indent < 0 ? 0 : indent;
345
// preceeding token is not opening tag, keep same indentation
346
changeRowIndent(doc, dotPos, indent);
350
@Override public int[] getReformatBlock(JTextComponent target, String typedText) {
351
BaseDocument doc = Utilities.getDocument(target);
353
if (!hasValidSyntaxSupport(doc)){
357
char lastChar = typedText.charAt(typedText.length() - 1);
360
int dotPos = target.getCaret().getDot();
362
if (lastChar == '>') {
363
TokenItem tknPrecedingToken = getTagTokenEndingAtPosition(doc, dotPos - 1);
365
if (isClosingTag(tknPrecedingToken)){
366
// the user has just entered a closing tag
367
// - reformat it unless matching opening tag is on the same line
369
TokenItem tknOpeningTag = getMatchingOpeningTag(tknPrecedingToken);
371
if (tknOpeningTag != null){
372
int openingTagLine = Utilities.getLineOffset(doc, tknOpeningTag.getOffset());
373
int closingTagSymbolLine = Utilities.getLineOffset(doc, dotPos);
375
if(openingTagLine != closingTagSymbolLine){
376
return new int[]{tknPrecedingToken.getOffset(), dotPos};
382
else if(lastChar == '\n') {
383
// just pressed enter
384
enterPressed(target, dotPos);
387
} catch (Exception e){
388
ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
394
protected TokenItem getMatchingOpeningTag(TokenItem tknClosingTag){
395
String searchedTagName = extractTagName(tknClosingTag);
396
TokenItem token = tknClosingTag.getPrevious();
399
while (token != null){
400
if (areTagNamesEqual(searchedTagName, extractTagName(token))){
401
if (isOpeningTag(token)){
407
} else if (isClosingTag(token)){
412
token = token.getPrevious();
418
protected int getInitialIndentFromPreviousLine(final BaseDocument doc, final int line) throws BadLocationException {
420
// get initial indent from the previous line
421
int initialIndent = 0;
424
int lineStart = Utilities.getRowStartFromLineOffset(doc, line);
425
int previousNonWhiteLineEnd = Utilities.getFirstNonWhiteBwd(doc, lineStart);
427
if (previousNonWhiteLineEnd > 0){
428
initialIndent = Utilities.getRowIndent(doc, previousNonWhiteLineEnd);
432
return initialIndent;
435
private int getInitialIndentFromNextLine(final BaseDocument doc, final int line) throws BadLocationException {
437
// get initial indent from the next line
438
int initialIndent = 0;
440
int lineStart = Utilities.getRowStartFromLineOffset(doc, line);
441
int lineEnd = Utilities.getRowEnd(doc, lineStart);
442
int nextNonWhiteLineStart = Utilities.getFirstNonWhiteFwd(doc, lineEnd);
444
if (nextNonWhiteLineStart > 0){
445
initialIndent = Utilities.getRowIndent(doc, nextNonWhiteLineStart, true);
448
return initialIndent;
451
private boolean hasValidSyntaxSupport(BaseDocument doc){
452
ExtSyntaxSupport sup = getSyntaxSupport(doc);
455
ErrorManager.getDefault().log(ErrorManager.WARNING,
456
"TagBasedFormatter: failed to retrieve SyntaxSupport for document;" + //NOI18N
457
" probably attempt to use incompatible indentation engine"); //NOI18N
465
protected static int getNumberOfLines(BaseDocument doc) throws BadLocationException{
466
return Utilities.getLineOffset(doc, doc.getLength() - 1) + 1;
469
protected TokenItem getNextClosingTag(BaseDocument doc, int offset) throws BadLocationException{
470
ExtSyntaxSupport sup = getSyntaxSupport(doc);
471
TokenItem token = sup.getTokenChain(offset, offset + 1);
473
while (token != null){
474
if (isClosingTag(token)){
478
token = token.getNext();
484
protected boolean isJustBeforeClosingTag(BaseDocument doc, int pos) throws BadLocationException {
485
ExtSyntaxSupport sup = getSyntaxSupport(doc);
486
TokenItem tknTag = sup.getTokenChain(pos, pos + 1);
488
if (isClosingTag(tknTag)){
495
protected class InitialIndentData{
496
private final int indentLevelBias;
497
private final int indentBias;
498
private final int indentLevels[];
499
private final int indentsWithinTags[];
500
private BaseDocument doc;
502
public InitialIndentData(BaseDocument doc, int indentLevels[], int indentsWithinTags[],
503
int firstRefBlockLine, int lastRefBlockLine) throws BadLocationException{
505
int initialIndent = getInitialIndentFromPreviousLine(doc, firstRefBlockLine);
506
int indentLevelBiasFromTheTop = initialIndent / doc.getShiftWidth() - (firstRefBlockLine > 0 ? indentLevels[firstRefBlockLine - 1] : 0);
508
int initialIndentFromTheBottom = getInitialIndentFromNextLine(doc, lastRefBlockLine);
509
int indentLevelBiasFromTheBottom = initialIndentFromTheBottom / doc.getShiftWidth() - (lastRefBlockLine < getNumberOfLines(doc) - 1 ? indentLevels[lastRefBlockLine + 1] : 0);
511
if (indentLevelBiasFromTheBottom > indentLevelBiasFromTheTop){
512
indentLevelBias = indentLevelBiasFromTheBottom;
513
initialIndent = initialIndentFromTheBottom;
516
indentLevelBias = indentLevelBiasFromTheTop;
519
indentBias = initialIndent % doc.getShiftWidth();
520
this.indentLevels = indentLevels;
521
this.indentsWithinTags = indentsWithinTags;
525
public boolean isEligibleToIndent(int line){
526
return getActualIndentLevel(line) >= 0;
529
public int getIndent(int line){
530
return indentBias + indentsWithinTags[line] + getActualIndentLevel(line) * doc.getShiftWidth();
533
private int getActualIndentLevel(int line){
534
return indentLevels[line] + indentLevelBias;
538
protected static class TagIndentationData{
539
private final String tagName;
540
private final int line;
541
private int closedOnLine;
543
public TagIndentationData(String tagName, int line){
544
this.tagName = tagName;
548
public String getTagName() {
552
public int getLine() {
556
public int getClosedOnLine() {
560
public void setClosedOnLine(int closedOnLine) {
561
this.closedOnLine = closedOnLine;