Lines 1-7
Link Here
|
1 |
/* |
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
3 |
* |
4 |
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. |
4 |
* Copyright 1997-2010 Sun Microsystems, Inc. All rights reserved. |
5 |
* |
5 |
* |
6 |
* The contents of this file are subject to the terms of either the GNU |
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 |
7 |
* General Public License Version 2 only ("GPL") or the Common |
Lines 42-50
Link Here
|
42 |
|
42 |
|
43 |
import java.io.IOException; |
43 |
import java.io.IOException; |
44 |
import java.util.ArrayList; |
44 |
import java.util.ArrayList; |
|
|
45 |
import java.util.LinkedList; |
45 |
import java.util.List; |
46 |
import java.util.List; |
46 |
import java.util.Stack; |
47 |
import java.util.Stack; |
47 |
import javax.swing.text.AbstractDocument; |
|
|
48 |
import javax.swing.text.BadLocationException; |
48 |
import javax.swing.text.BadLocationException; |
49 |
import javax.swing.text.Element; |
49 |
import javax.swing.text.Element; |
50 |
import org.netbeans.api.lexer.LanguagePath; |
50 |
import org.netbeans.api.lexer.LanguagePath; |
Lines 53-59
Link Here
|
53 |
import org.netbeans.api.lexer.TokenSequence; |
53 |
import org.netbeans.api.lexer.TokenSequence; |
54 |
import org.netbeans.api.xml.lexer.XMLTokenId; |
54 |
import org.netbeans.api.xml.lexer.XMLTokenId; |
55 |
import org.netbeans.editor.BaseDocument; |
55 |
import org.netbeans.editor.BaseDocument; |
56 |
import org.netbeans.editor.Formatter; |
|
|
57 |
import org.netbeans.editor.Utilities; |
56 |
import org.netbeans.editor.Utilities; |
58 |
import org.netbeans.modules.editor.indent.api.IndentUtils; |
57 |
import org.netbeans.modules.editor.indent.api.IndentUtils; |
59 |
import org.netbeans.modules.editor.indent.spi.Context; |
58 |
import org.netbeans.modules.editor.indent.spi.Context; |
Lines 61-67
Link Here
|
61 |
import org.netbeans.modules.editor.structure.formatting.TagBasedLexerFormatter; |
60 |
import org.netbeans.modules.editor.structure.formatting.TagBasedLexerFormatter; |
62 |
import org.netbeans.modules.xml.text.folding.TokenElement; |
61 |
import org.netbeans.modules.xml.text.folding.TokenElement; |
63 |
import org.netbeans.modules.xml.text.folding.TokenElement.TokenType; |
62 |
import org.netbeans.modules.xml.text.folding.TokenElement.TokenType; |
64 |
import org.netbeans.modules.xml.text.syntax.XMLKit; |
63 |
import org.netbeans.modules.xml.text.folding.XmlFoldManager; |
65 |
|
64 |
|
66 |
/** |
65 |
/** |
67 |
* New XML formatter based on Lexer APIs. |
66 |
* New XML formatter based on Lexer APIs. |
Lines 218-240
Link Here
|
218 |
} |
217 |
} |
219 |
|
218 |
|
220 |
// # 170343 |
219 |
// # 170343 |
221 |
// @Override |
220 |
@Override |
222 |
// public void reformat(Context context, final int startOffset, final int endOffset) |
221 |
public void reformat(Context context, final int startOffset, final int endOffset) |
223 |
// throws BadLocationException { |
222 |
throws BadLocationException { |
224 |
// final BaseDocument doc = (BaseDocument) context.document(); |
223 |
final BaseDocument doc = (BaseDocument) context.document(); |
225 |
// doc.render(new Runnable() { |
224 |
doc.render(new Runnable() { |
226 |
// public void run() { |
225 |
|
227 |
// doReformat(doc, startOffset, endOffset); |
226 |
public void run() { |
228 |
// } |
227 |
doReformat(doc, startOffset, endOffset); |
229 |
// }); |
228 |
} |
230 |
// } |
229 |
}); |
|
|
230 |
} |
231 |
|
231 |
|
232 |
public BaseDocument doReformat(BaseDocument doc, int startOffset, int endOffset) { |
232 |
public BaseDocument doReformat(BaseDocument doc, int startOffset, int endOffset) { |
233 |
spacesPerTab = IndentUtils.indentLevelSize(doc); |
233 |
spacesPerTab = IndentUtils.indentLevelSize(doc); |
234 |
try { |
234 |
try { |
235 |
List<TokenElement> tags = getTags(doc, startOffset, endOffset); |
235 |
List<TokenIndent> tags = getTags(doc, startOffset, endOffset); |
236 |
for (int i = tags.size() - 1; i >= 0; i--) { |
236 |
for (int i = tags.size() - 1; i >= 0; i--) { |
237 |
TokenElement tag = tags.get(i); |
237 |
if (tags.get(i).isPreserveIndent()) { |
|
|
238 |
continue; |
239 |
} |
240 |
TokenElement tag = tags.get(i).getToken(); |
241 |
|
238 |
int so = tag.getStartOffset(); |
242 |
int so = tag.getStartOffset(); |
239 |
int lineOffset = Utilities.getLineOffset(doc, so); |
243 |
int lineOffset = Utilities.getLineOffset(doc, so); |
240 |
String tagName = tag.getName(); |
244 |
String tagName = tag.getName(); |
Lines 252-258
Link Here
|
252 |
int ndx = lineStr.lastIndexOf(tagName); |
256 |
int ndx = lineStr.lastIndexOf(tagName); |
253 |
if (ndx != -1) { |
257 |
if (ndx != -1) { |
254 |
lineStr = lineStr.substring(0, ndx); |
258 |
lineStr = lineStr.substring(0, ndx); |
255 |
int ndx2 = lineStr.lastIndexOf("<" + tagName.substring(2) ); |
259 |
int ndx2 = lineStr.lastIndexOf("<" + tagName.substring(2)); |
256 |
if (ndx2 == -1) {//no start found in this line, so indent this tag |
260 |
if (ndx2 == -1) {//no start found in this line, so indent this tag |
257 |
changePrettyText(doc, tag, so); |
261 |
changePrettyText(doc, tag, so); |
258 |
} else { |
262 |
} else { |
Lines 266-285
Link Here
|
266 |
} else { |
270 |
} else { |
267 |
changePrettyText(doc, tag, so); |
271 |
changePrettyText(doc, tag, so); |
268 |
} |
272 |
} |
269 |
} |
273 |
} |
270 |
} catch (BadLocationException ble) { |
274 |
} catch (BadLocationException ble) { |
271 |
//ignore exception |
275 |
//ignore exception |
272 |
} catch (IOException iox) { |
276 |
} catch (IOException iox) { |
273 |
//ignore exception |
277 |
//ignore exception |
274 |
} finally { |
278 |
} finally { |
275 |
//((AbstractDocument)doc).readUnlock(); |
279 |
//((AbstractDocument)doc).readUnlock(); |
276 |
} |
280 |
} |
277 |
return doc; |
281 |
return doc; |
278 |
} |
282 |
} |
279 |
|
283 |
|
280 |
private void changePrettyText(BaseDocument doc, TokenElement tag, int so) throws BadLocationException { |
284 |
private void changePrettyText(BaseDocument doc, TokenElement tag, int so) throws BadLocationException { |
281 |
Formatter formatter = Formatter.getFormatter(XMLKit.class); |
|
|
282 |
formatter.setExpandTabs(false); |
283 |
//i expected the call IndentUtils.createIndentString() to return |
285 |
//i expected the call IndentUtils.createIndentString() to return |
284 |
//the correct string for the indent level, but it doesn't. |
286 |
//the correct string for the indent level, but it doesn't. |
285 |
//so this is just a workaround. |
287 |
//so this is just a workaround. |
Lines 292-345
Link Here
|
292 |
int i = Utilities.getRowFirstNonWhite(doc, so); |
294 |
int i = Utilities.getRowFirstNonWhite(doc, so); |
293 |
int rowStart = Utilities.getRowStart(doc, so); |
295 |
int rowStart = Utilities.getRowStart(doc, so); |
294 |
doc.insertString(so, newIndentText, null); |
296 |
doc.insertString(so, newIndentText, null); |
295 |
doc.remove(rowStart, i - rowStart); |
297 |
doc.remove(rowStart, i - rowStart); |
296 |
} |
298 |
} |
297 |
else { |
299 |
else { |
298 |
doc.insertString(so, "\n" + newIndentText, null); |
300 |
doc.insertString(so, "\n" + newIndentText, null); |
299 |
} |
301 |
} |
300 |
} |
302 |
} |
301 |
|
303 |
|
302 |
/** |
304 |
/** |
303 |
* This is the core of the fold creation algorithm. |
305 |
* This is the core of the formatting algorithm. It was originally derived |
304 |
* This method parses the document using lexer and creates folds and adds |
306 |
* from {@link XmlFoldManager#createFolds(org.netbeans.spi.editor.fold.FoldHierarchyTransaction)}. |
305 |
* them to the fold hierarchy. |
307 |
* Like that method, this method parses the document using lexer. Rather |
|
|
308 |
* than creating folds though, this method reformats by manipulating the |
309 |
* whitespace tokens. To do this it keeps track of the nesting level of the |
310 |
* XML and the use of the xml:space attribute. Together they are used to |
311 |
* calculate how much each token should be indented by. |
306 |
*/ |
312 |
*/ |
307 |
private List<TokenElement> getTags(BaseDocument basedoc, int startOffset, int endOffset) |
313 |
private List<TokenIndent> getTags(BaseDocument basedoc, int startOffset, int endOffset) |
308 |
throws BadLocationException, IOException { |
314 |
throws BadLocationException, IOException { |
309 |
List<TokenElement> tags = new ArrayList<TokenElement>(); |
315 |
List<TokenIndent> tags = new ArrayList<TokenIndent>(); |
|
|
316 |
|
317 |
// List to keep track of whether whitespace is to be preserved at each |
318 |
// level of nesting. By default whitesapce is not preserved. |
319 |
LinkedList<Boolean> preserveNesting_outdent = new LinkedList<Boolean>(); |
320 |
preserveNesting_outdent.add(Boolean.FALSE); |
321 |
|
322 |
// flag that is true if whitespace is currently |
323 |
// to not be changed. That is, xml:space |
324 |
// was last set to "preserve". |
325 |
boolean preserveWhitespace = false; |
326 |
|
327 |
// flag to indicate if the current |
328 |
// argument is xml:space |
329 |
boolean settingSpaceValue = false; |
330 |
|
331 |
int indentLevel = -1; |
310 |
basedoc.readLock(); |
332 |
basedoc.readLock(); |
311 |
int incrIndentLevelBy = 0; |
|
|
312 |
try { |
333 |
try { |
313 |
//are we formatting from the beginning of doc or a subsection |
|
|
314 |
int line = Utilities.getLineOffset(basedoc, startOffset); |
315 |
if(line > 0) { |
316 |
boolean nested = isSubSectionToFormatNested(basedoc, startOffset); |
317 |
//we are formatting a subsection |
318 |
int precedingWordLoc = Utilities.getFirstNonWhiteBwd(basedoc, startOffset) ; |
319 |
int previousLine = Utilities.getLineOffset(basedoc, precedingWordLoc); |
320 |
int previousLineIndentation = 0; |
321 |
//we need to get the previous line and find its indentation |
322 |
//the previous line will be ( current line - 1), if the user has |
323 |
//has selected the entire line to format |
324 |
//the previous line will be the same as current line if user |
325 |
//selected a part of line to format |
326 |
if(previousLine != line){ |
327 |
previousLineIndentation = Utilities.getRowIndent(basedoc, precedingWordLoc); |
328 |
} else |
329 |
previousLineIndentation = Utilities.getRowIndent(basedoc, startOffset); |
330 |
//the section being formatted should be idented wrt to previous line's indentation |
331 |
|
332 |
int div = previousLineIndentation / spacesPerTab; |
333 |
if(nested) |
334 |
incrIndentLevelBy = div +1; |
335 |
else |
336 |
incrIndentLevelBy = div; |
337 |
|
338 |
} |
339 |
TokenHierarchy tokenHierarchy = TokenHierarchy.get(basedoc); |
334 |
TokenHierarchy tokenHierarchy = TokenHierarchy.get(basedoc); |
340 |
TokenSequence<XMLTokenId> tokenSequence = tokenHierarchy.tokenSequence(); |
335 |
TokenSequence<XMLTokenId> tokenSequence = tokenHierarchy.tokenSequence(); |
341 |
org.netbeans.api.lexer.Token<XMLTokenId> token = tokenSequence.token(); |
336 |
org.netbeans.api.lexer.Token<XMLTokenId> token = tokenSequence.token(); |
342 |
// Add the text token, if any, before xml decalration to document node |
337 |
// Add the text token, if any, before xml declaration to document node |
343 |
if (token != null && token.id() == XMLTokenId.TEXT) { |
338 |
if (token != null && token.id() == XMLTokenId.TEXT) { |
344 |
if (tokenSequence.moveNext()) { |
339 |
if (tokenSequence.moveNext()) { |
345 |
token = tokenSequence.token(); |
340 |
token = tokenSequence.token(); |
Lines 347-367
Link Here
|
347 |
} |
342 |
} |
348 |
int currentTokensSize = 0; |
343 |
int currentTokensSize = 0; |
349 |
Stack<TokenElement> stack = new Stack<TokenElement>(); |
344 |
Stack<TokenElement> stack = new Stack<TokenElement>(); |
350 |
String currentNode = null; |
|
|
351 |
while (tokenSequence.moveNext()) { |
345 |
while (tokenSequence.moveNext()) { |
352 |
token = tokenSequence.token(); |
346 |
token = tokenSequence.token(); |
353 |
XMLTokenId tokenId = token.id(); |
347 |
XMLTokenId tokenId = token.id(); |
354 |
String image = token.text().toString(); |
348 |
String image = token.text().toString(); |
355 |
if( ! (tokenSequence.offset() >= startOffset && tokenSequence.offset() <endOffset) ) { |
349 |
if (tokenSequence.offset() > endOffset) { |
356 |
currentTokensSize += image.length(); |
350 |
break; |
357 |
continue; |
|
|
358 |
} |
351 |
} |
|
|
352 |
boolean tokenInSelectionRange = tokenSequence.offset() >= startOffset; |
359 |
TokenType tokenType = TokenType.TOKEN_WHITESPACE; |
353 |
TokenType tokenType = TokenType.TOKEN_WHITESPACE; |
360 |
switch (tokenId) { |
354 |
switch (tokenId) { |
361 |
case TAG: { |
355 |
case TAG: { // Tag is encountered and the required level of indenting determined. |
|
|
356 |
// The tokens are only assessed if they are in the selection |
357 |
// range, which is the whole document if no text is selected. |
362 |
int len = image.length(); |
358 |
int len = image.length(); |
363 |
if (image.charAt(len - 1) == '>') {// '/>' |
359 |
if (image.charAt(len - 1) == '>') {// '/>' |
364 |
if (len == 2) { |
360 |
if (len == 2) { |
|
|
361 |
if (!preserveWhitespace) { |
362 |
--indentLevel; |
363 |
} |
365 |
if (!stack.empty()) { |
364 |
if (!stack.empty()) { |
366 |
stack.pop(); |
365 |
stack.pop(); |
367 |
} |
366 |
} |
Lines 369-395
Link Here
|
369 |
} else { |
368 |
} else { |
370 |
tokenType = TokenType.TOKEN_ELEMENT_START_TAG; |
369 |
tokenType = TokenType.TOKEN_ELEMENT_START_TAG; |
371 |
if (image.startsWith("</")) { |
370 |
if (image.startsWith("</")) { |
372 |
String tagName = image.substring(2); |
|
|
373 |
currentNode = tagName; |
374 |
int begin = currentTokensSize; |
371 |
int begin = currentTokensSize; |
375 |
int end = begin + image.length(); |
372 |
int end = begin + image.length(); |
376 |
int indentLevel = incrIndentLevelBy; |
373 |
|
377 |
if (!stack.empty()) { |
374 |
boolean preservingWhitespaceOnClose = preserveNesting_outdent.removeLast(); |
378 |
stack.pop(); |
375 |
if (indentLevel < 0) { |
379 |
indentLevel = stack.size() +incrIndentLevelBy; |
376 |
indentLevel = 0; |
380 |
} |
377 |
} |
381 |
|
378 |
if (tokenInSelectionRange) { |
382 |
TokenElement tag = new TokenElement(tokenType, image, begin, end, indentLevel); |
379 |
TokenElement tag = new TokenElement(tokenType, image, begin, end, indentLevel); |
383 |
tags.add(tag); |
380 |
tags.add(new TokenIndent(tag, preservingWhitespaceOnClose)); |
|
|
381 |
} |
382 |
if (!preserveNesting_outdent.getLast()) { |
383 |
--indentLevel; |
384 |
} |
384 |
} else { |
385 |
} else { |
385 |
String tagName = image.substring(1); |
386 |
String tagName = image.substring(1); |
386 |
int begin = currentTokensSize; |
387 |
int begin = currentTokensSize; |
387 |
int end = begin + image.length(); |
388 |
int end = begin + image.length(); |
388 |
int indentLevel = stack.size() + incrIndentLevelBy; |
389 |
preserveWhitespace = preserveNesting_outdent.getLast(); |
389 |
TokenElement tag = new TokenElement(tokenType, tagName, begin, end, indentLevel); |
390 |
if (!preserveWhitespace) { |
390 |
tags.add(tag); |
391 |
++indentLevel; |
391 |
stack.push(tag); |
392 |
} |
|
|
393 |
preserveNesting_outdent.add(preserveWhitespace); |
394 |
if (tokenInSelectionRange) { |
395 |
TokenElement tag = new TokenElement(tokenType, tagName, begin, end, indentLevel); |
396 |
tags.add(new TokenIndent(tag, preserveWhitespace)); |
397 |
} |
392 |
} |
398 |
} |
|
|
399 |
settingSpaceValue = false; |
393 |
} |
400 |
} |
394 |
break; |
401 |
break; |
395 |
} |
402 |
} |
Lines 399-418
Link Here
|
399 |
case PI_TARGET: |
406 |
case PI_TARGET: |
400 |
case PI_CONTENT: |
407 |
case PI_CONTENT: |
401 |
case PI_END: |
408 |
case PI_END: |
402 |
case ARGUMENT: //attribute of an element |
|
|
403 |
case VALUE: |
404 |
case TEXT: |
409 |
case TEXT: |
405 |
case CHARACTER: |
410 |
case CHARACTER: |
406 |
case WS: |
411 |
case WS: |
407 |
case OPERATOR: |
412 |
case OPERATOR: |
408 |
case DECLARATION: |
413 |
case DECLARATION: |
409 |
break; //Do nothing for above case's |
414 |
break; //Do nothing for above case's |
|
|
415 |
case ARGUMENT: //attribute of an element |
416 |
settingSpaceValue = token.text().equals("xml:space"); |
417 |
break; |
418 |
case VALUE: |
419 |
if (settingSpaceValue) { |
420 |
if (token.text().equals("\"preserve\"")) { |
421 |
preserveWhitespace = true; |
422 |
} else if (token.text().equals("\"default\"")) { |
423 |
preserveWhitespace = false; |
424 |
} |
425 |
preserveNesting_outdent.set(preserveNesting_outdent.size() - 1, preserveWhitespace); |
426 |
settingSpaceValue = false; |
427 |
} |
428 |
break; |
410 |
|
429 |
|
411 |
case ERROR: |
430 |
case ERROR: |
412 |
case EOL: |
431 |
case EOL: |
413 |
default: |
432 |
default: |
414 |
throw new IOException("Invalid token found in document: " + |
433 |
throw new IOException("Invalid token found in document: " |
415 |
"Please use the text editor to resolve the issues..."); |
434 |
+ "Please use the text editor to resolve the issues..."); |
416 |
} |
435 |
} |
417 |
currentTokensSize += image.length(); |
436 |
currentTokensSize += image.length(); |
418 |
} |
437 |
} |
Lines 431-459
Link Here
|
431 |
return false; |
450 |
return false; |
432 |
} |
451 |
} |
433 |
} |
452 |
} |
434 |
|
453 |
|
435 |
private boolean isSubSectionToFormatNested(BaseDocument baseDoc, int startOffset){ |
454 |
/** |
436 |
AbstractDocument doc = (AbstractDocument)baseDoc; |
455 |
* The formatter needs to keep track of when it can remove whitespace and |
437 |
doc.readLock(); |
456 |
* when it must preserve whitespace as defined by the xml:space attribute. |
438 |
try { |
457 |
* This class associates a flag that defines whether whitespace is to be |
439 |
TokenHierarchy th = TokenHierarchy.get(doc); |
458 |
* preserved with the other token data that is used in the code folding |
440 |
TokenSequence ts = th.tokenSequence(); |
459 |
* algorithm. |
441 |
ts.move(startOffset); |
460 |
*/ |
442 |
while(ts.movePrevious()) { |
461 |
private class TokenIndent { |
443 |
Token t = ts.token(); |
462 |
|
444 |
if(t.id() == XMLTokenId.PI_END) |
463 |
private TokenElement token; |
445 |
return false; |
464 |
private boolean preserveIndent; |
446 |
String tagText = t.text().toString(); |
465 |
|
447 |
if(tagText.startsWith("</") || tagText.equals("/>")) |
466 |
public TokenIndent(TokenElement token, boolean preserveIndent) { |
448 |
return false; |
467 |
this.token = token; |
449 |
if(tagText.startsWith("<")) |
468 |
this.preserveIndent = preserveIndent; |
450 |
return true; |
469 |
} |
451 |
} |
470 |
|
452 |
} catch (Exception ex) { |
471 |
public TokenElement getToken() { |
453 |
//return false anyway |
472 |
return token; |
454 |
} finally { |
473 |
} |
455 |
doc.readUnlock(); |
474 |
|
456 |
} |
475 |
public boolean isPreserveIndent() { |
457 |
return false; |
476 |
return preserveIndent; |
|
|
477 |
} |
478 |
|
479 |
public void setPreserveIndent(boolean preserveIndent) { |
480 |
this.preserveIndent = preserveIndent; |
481 |
} |
482 |
|
483 |
@Override |
484 |
public String toString() { |
485 |
return "TokenIndent: name=" + token.getName() + " preserveIndent=" + preserveIndent; |
486 |
} |
458 |
} |
487 |
} |
459 |
} |
488 |
} |