|
What this is
Other links
The source code/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor.ext; import java.io.IOException; import java.io.Writer; import java.util.List; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Position; import org.netbeans.editor.BaseKit; import org.netbeans.editor.BaseDocument; import org.netbeans.editor.Syntax; import org.netbeans.editor.SettingsNames; import org.netbeans.editor.TokenID; import org.netbeans.editor.TokenContextPath; import org.netbeans.editor.TokenItem; import org.netbeans.editor.Utilities; import org.netbeans.editor.EditorDebug; /** * Formatting writter accepts the input-text, formats it * and writes the output to the underlying writer. * The data written to the writer are immediately splitted * into token-items and the chain of token-items is created * from them. Then the writer then waits for the flush() or close() * method call which then makes the real formatting. * The formatting is done through going through the format-layers * registered in ExtFormatter and asking them for formatting. * These layers go through the chain and possibly add or remove * the tokens as necessary. The good thing is that the layers * can ask the tokens before those written to the writer. * In that case they will get the tokens from the document at point * the formatting should be done. The advantage is that the chain * is compact so the border between the tokens written to the writer * and those that come from the document is invisible. * * @author Miloslav Metelka * @version 1.00 */ public final class FormatWriter extends Writer { /** Whether debug messages should be displayed */ public static final boolean debug = Boolean.getBoolean("netbeans.debug.editor.format"); // NOI18N /** Whether debug messages should be displayed */ public static final boolean debugModify = Boolean.getBoolean("netbeans.debug.editor.format.modify"); // NOI18N private static final char[] EMPTY_BUFFER = new char[0]; /** Formatter related to this format-writer */ private ExtFormatter formatter; /** Document being formatted */ private Document doc; /** Offset at which the formatting occurs */ private int offset; /** Underlying writer */ private Writer underWriter; /** Syntax scanning the characters passed to the writer. For non-BaseDocuments * it also scans the characters preceding the format offset. The goal here * is to maintain the formatted tokens consistent with the scanning context * at the offset in the document. This is achieved differently * depending on whether the document is an instance of the BaseDocument * or not. * If the document is an instance of the BaseDocument, * the syntax is used solely for the scanning of the formatted tokens * and it is first prepared to scan right after the offset. * For non-BaseDocuments the syntax first scans the whole are * from the begining of the document till the offset and then continues * on with the formatted tokens. */ private Syntax syntax; /** Whether the purpose is to find an indentation * instead of formatting the tokens. In this mode only the '\n' is written * to the format-writer, and the layers should insert the appropriate * white-space tokens before the '\n' that will form * the indentation of the line. */ private boolean indentOnly; /** Buffer being scanned */ private char[] buffer; /** Number of the valid chars in the buffer */ private int bufferSize; /** Support for creating the positions. */ private FormatTokenPositionSupport ftps; /** Prescan at the offset position */ private int offsetPreScan; /** Whether the first flush() is being done. * it must respect the offsetPreScan. */ private boolean firstFlush; /** Last token-item in the chain */ private ExtTokenItem lastToken; /** Position where the formatting should start. */ private FormatTokenPosition formatStartPosition; /** The first position that doesn't belong to the document. */ private FormatTokenPosition textStartPosition; /** This flag is set automatically if the new removal or insertion * into chain occurs. The formatter can use this flag to detect whether * a particular format-layer changed the chain. */ private boolean chainModified; /** Whether the format should be restarted. */ private boolean restartFormat; /** Flag that helps to avoid unnecessary formatting when * calling flush() periodically without calling write() */ private boolean lastFlush; /** Shift resulting indentation position to which the caret is moved. * By default the caret goes to the first non-whitespace character * on the formatted line. If the line is empty then to the end of the * indentation whitespace. This variable enables to move the resulting * position either left or right. */ private int indentShift; /** Whether this format writer runs in the simple mode. * In simple mode the input is directly written to output. */ private boolean simple; /** Added to fix #5620 */ private boolean reformatting; /** Added to fix #5620 */ void setReformatting(boolean reformatting) { this.reformatting = reformatting; } /** The format writers should not be extended to enable * operating of the layers on all the writers even for different * languages. * @param underWriter underlying writer */ FormatWriter(ExtFormatter formatter, Document doc, int offset, Writer underWriter, boolean indentOnly) { this.formatter = formatter; this.doc = doc; this.offset = offset; this.underWriter = underWriter; this.setIndentOnly(indentOnly); if (debug) { System.err.println("FormatWriter() created, formatter=" + formatter // NOI18N + ", document=" + doc.getClass() + ", expandTabs=" + formatter.expandTabs() // NOI18N + ", spacesPerTab=" + formatter.getSpacesPerTab() // NOI18N + ", tabSize=" + ((doc instanceof BaseDocument) // NOI18N ? ((BaseDocument)doc).getTabSize() : formatter.getTabSize()) + ", shiftWidth=" + ((doc instanceof BaseDocument) // NOI18N ? ((BaseDocument)doc).getShiftWidth() : formatter.getShiftWidth()) ); } // Return now for simple formatter if (formatter.isSimple()) { simple = true; return; } buffer = EMPTY_BUFFER; firstFlush = true; // Hack for getting the right kit and then syntax Class kitClass = (doc instanceof BaseDocument) ? ((BaseDocument)doc).getKitClass() : formatter.getKitClass(); syntax = (kitClass != BaseKit.class) ? BaseKit.getKit(kitClass).createFormatSyntax(doc) : new org.netbeans.editor.ext.plain.PlainSyntax(); if (!formatter.acceptSyntax(syntax)) { simple = true; // turn to simple format writer } ftps = new FormatTokenPositionSupport(this); if (doc instanceof BaseDocument) { try { BaseDocument bdoc = (BaseDocument)doc; /* Init syntax right at the formatting offset so it will * contain the prescan characters only. The non-last-buffer * is inforced (even when at the document end) because the * text will follow the current document text. */ bdoc.getSyntaxSupport().initSyntax(syntax, offset, offset, false, true); offsetPreScan = syntax.getPreScan(); if (debug) { System.err.println("FormatWriter: preScan=" + offsetPreScan + " at offset=" + offset); } if (offset > 0) { // only if not formatting from the start of the document ExtSyntaxSupport sup = (ExtSyntaxSupport)bdoc.getSyntaxSupport(); Integer lines = (Integer)bdoc.getProperty(SettingsNames.LINE_BATCH_SIZE); int startOffset = Utilities.getRowStart(bdoc, Math.max(offset - offsetPreScan, 0), -Math.max(lines.intValue(), 1) ); if (startOffset < 0) { // invalid line startOffset = 0; } // Parse tokens till the offset TokenItem ti = sup.getTokenChain(startOffset, offset); if (ti != null && ti.getOffset() < offset - offsetPreScan) { lastToken = new FilterDocumentItem(ti, null, false); if (debug) { System.err.println("FormatWriter: first doc token=" + lastToken); // NOI18N } // Iterate through the chain till the last item while (lastToken.getNext() != null && lastToken.getNext().getOffset() < offset - offsetPreScan ) { lastToken = (ExtTokenItem)lastToken.getNext(); if (debug) { System.err.println("FormatWriter: doc token=" + lastToken); // NOI18N } } // Terminate the end of chain so it doesn't try // to append the next token from the document ((FilterDocumentItem)lastToken).terminate(); } } } catch (BadLocationException e) { Utilities.annotateLoggable(e); } } else { // non-BaseDocument try { String text = doc.getText(0, offset); char[] buffer = text.toCharArray(); // Force non-last buffer syntax.load(null, buffer, 0, buffer.length, false, 0); TokenID tokenID = syntax.nextToken(); while (tokenID != null) { int tokenOffset = syntax.getTokenOffset(); lastToken = new FormatTokenItem(tokenID, syntax.getTokenContextPath(), tokenOffset, text.substring(tokenOffset, tokenOffset + syntax.getTokenLength()), lastToken ); if (debug) { System.err.println("FormatWriter: non-bd token=" + lastToken); } ((FormatTokenItem)lastToken).markWritten(); tokenID = syntax.nextToken(); } // Assign the preScan offsetPreScan = syntax.getPreScan(); } catch (BadLocationException e) { Utilities.annotateLoggable(e); } } // Write the preScan characters char[] buf = syntax.getBuffer(); int bufOffset = syntax.getOffset(); if (debug) { System.err.println("FormatWriter: writing preScan chars='" // NOI18N + EditorDebug.debugChars(buf, bufOffset - offsetPreScan, offsetPreScan) + "'" // NOI18N + ", length=" + offsetPreScan // NOI18N ); } // Write the preScan chars to the buffer addToBuffer(buf, bufOffset - offsetPreScan, offsetPreScan); } public final ExtFormatter getFormatter() { return formatter; } /** Get the document being formatted */ public final Document getDocument() { return doc; } /** Get the starting offset of the formatting */ public final int getOffset() { return offset; } /** Whether the purpose of this writer is to find the proper indentation * instead of formatting the tokens. It allows to have a modified * formatting behavior for the cases when user presses Enter or a key * that causes immediate reformatting of the line. */ public final boolean isIndentOnly() { return indentOnly; } /** Sets whether the purpose of this writer is to find the proper indentation * instead of formatting the tokens. * @see isIndentOnly() */ public void setIndentOnly(boolean indentOnly) { this.indentOnly = indentOnly; } /** Get the first token that should be formatted. * This can change as the format-layers continue to change the token-chain. * If the caller calls flush(), this method will return null. After * additional writing to the writer, new tokens will be added and * the first one of them will become the first token to be formatted. * @return the first token that should be formatted. It can be null * in case some layer removes all the tokens that should be formatted. * Most of the layers probably do nothing in case this value is null. */ public FormatTokenPosition getFormatStartPosition() { return formatStartPosition; } /** Get the first position that doesn't belong to the document. * Initially it's the same as the getFormatStartPosition() but * if there are multiple flushes performed on the writer they will differ. */ public FormatTokenPosition getTextStartPosition() { return textStartPosition; } /** Get the last token in the chain. It can be null * if there are no tokens in the chain. */ public TokenItem getLastToken() { return lastToken; } /** Find the first token in the chain. It should be used only when necessary * and possibly in situations when the start of the chain * was already reached by other methods, because this method * will extend the chain till the begining of the document. * @param token token from which the search for previous tokens will * start. It can be null in which case the last document token or last * token are attempted instead. */ public TokenItem findFirstToken(TokenItem token) { if (token == null) { // Try textStartPosition first token = (textStartPosition != null) ? textStartPosition.getToken() : null; if (token == null) { // Try starting of the formatting position next token = formatStartPosition.getToken(); if (token == null) { token = lastToken; if (token == null) { return null; } } } } while (token.getPrevious() != null) { token = token.getPrevious(); } return token; } /** It checks whether the tested token is after some other token in the chain. * @param testedToken token to test (whether it's after afterToken or not) * @param afterToken token to be compared to the testedToken * @return true whether the testedToken is after afterToken or not. * Returns false if the token == afterToken * or not or if token is before the afterToken or not. */ public boolean isAfter(TokenItem testedToken, TokenItem afterToken) { while (afterToken != null) { afterToken = afterToken.getNext(); if (afterToken == testedToken) { return true; } } return false; } /** Checks whether the tested position is after some other position. */ public boolean isAfter(FormatTokenPosition testedPosition, FormatTokenPosition afterPosition) { if (testedPosition.getToken() == afterPosition.getToken()) { return (testedPosition.getOffset() > afterPosition.getOffset()); } else { // different tokens return isAfter(testedPosition.getToken(), afterPosition.getToken()); } } /** Check whether the given token has empty text and if so * start searching for token with non-empty text in the given * direction. If there's no non-empty token in the given direction * the method returns null. * @param token token to start to search from. If it has zero * length, the search for non-empty token is performed in the given * direction. */ public TokenItem findNonEmptyToken(TokenItem token, boolean backward) { while (token != null && token.getImage().length() == 0) { token = backward ? token.getPrevious() : token.getNext(); } return token; } /** Check whether a new token can be inserted into the chain * before the given token-item. The token * can be inserted only into the tokens that come * from the text that was written to the format-writer * but was not yet written to the underlying writer. * @param beforeToken token-item before which * the new token-item is about to be inserted. It can * be null to append the new token to the end of the chain. */ public boolean canInsertToken(TokenItem beforeToken) { return beforeToken== null // appending to the end || !((ExtTokenItem)beforeToken).isWritten(); } /** Create a new token-item and insert it before * the token-item given as parameter. * The canInsertToken() should be called * first to determine whether the given token can * be inserted into the chain or not. The token * can be inserted only into the tokens that come * from the text that was written to the format-writer * but was not yet written to the underlying writer. * @param beforeToken token-item before which * the new token-item is about to be inserted. It can * be null to append the new token to the end of the chain. * @param tokenID token-id of the new token-item * @param tokenContextPath token-context-path of the new token-item * @param tokenImage image of the new token-item */ public TokenItem insertToken(TokenItem beforeToken, TokenID tokenID, TokenContextPath tokenContextPath, String tokenImage) { if (debugModify) { System.err.println("FormatWriter.insertToken(): beforeToken=" + beforeToken // NOI18N + ", tokenID=" + tokenID + ", contextPath=" + tokenContextPath // NOI18N + ", tokenImage='" + tokenImage + "'" // NOI18N ); } if (!canInsertToken(beforeToken)) { throw new IllegalStateException("Can't insert token into chain"); // NOI18N } // #5620 if (reformatting) { try { doc.insertString(getDocOffset(beforeToken), tokenImage, null); } catch (BadLocationException e) { e.printStackTrace(); } } FormatTokenItem fti; if (beforeToken != null) { fti = ((FormatTokenItem)beforeToken).insertToken(tokenID, tokenContextPath, -1, tokenImage); } else { // beforeToken is null fti = new FormatTokenItem(tokenID, tokenContextPath, -1, tokenImage, lastToken); lastToken = fti; } // Update token-positions ftps.tokenInsert(fti); chainModified = true; return fti; } /** Added to fix #5620 */ private int getDocOffset(TokenItem token) { int len = 0; if (token != null) { token = token.getPrevious(); } else { // after last token token = lastToken; } while (token != null) { len += token.getImage().length(); if (token instanceof FilterDocumentItem) { return len + token.getOffset(); } token = token.getPrevious(); } return len; } /** Whether the token-item can be removed. It can be removed * only in case it doesn't come from the document's text * and it wasn't yet written to the underlying writer. */ public boolean canRemoveToken(TokenItem token) { return !((ExtTokenItem)token).isWritten(); } /** Remove the token-item from the chain. It can be removed * only in case it doesn't come from the document's text * and it wasn't yet written to the underlying writer. */ public void removeToken(TokenItem token) { if (debugModify) { System.err.println("FormatWriter.removeToken(): token=" + token); // NOI18N } if (!canRemoveToken(token)) { if (true) { // !!! return; } throw new IllegalStateException("Can't remove token from chain"); // NOI18N } // #5620 if (reformatting) { try { doc.remove(getDocOffset(token), token.getImage().length()); } catch (BadLocationException e) { e.printStackTrace(); } } // Update token-positions ftps.tokenRemove(token); if (lastToken == token) { lastToken = (ExtTokenItem)token.getPrevious(); } ((FormatTokenItem)token).remove(); // remove self from chain chainModified = true; } public boolean canSplitStart(TokenItem token, int startLength) { return !((ExtTokenItem)token).isWritten(); } /** Create the additional token from the text at the start * of the given token. * @param token token being split. * @param startLength length of the text at the begining of the token * for which the additional token will be created. * @param tokenID token-id that will be assigned to the new token * @param tokenContextPath token-context-path that will be assigned * to the new token */ public TokenItem splitStart(TokenItem token, int startLength, TokenID newTokenID, TokenContextPath newTokenContextPath) { if (!canSplitStart(token, startLength)) { throw new IllegalStateException("Can't split the token=" + token); // NOI18N } String text = token.getImage(); if (startLength > text.length()) { throw new IllegalArgumentException("startLength=" + startLength // NOI18N + " is greater than token length=" + text.length()); // NOI18N } String newText = text.substring(0, startLength); ExtTokenItem newToken = (ExtTokenItem)insertToken(token, newTokenID, newTokenContextPath, newText); // Update token-positions ftps.splitStartTokenPositions(token, startLength); remove(token, 0, startLength); return newToken; } public boolean canSplitEnd(TokenItem token, int endLength) { int splitOffset = token.getImage().length() - endLength; return (((ExtTokenItem)token).getWrittenLength() <= splitOffset); } /** Create the additional token from the text at the end * of the given token. * @param token token being split. * @param endLength length of the text at the end of the token * for which the additional token will be created. * @param tokenID token-id that will be assigned to the new token * @param tokenContextPath token-context-path that will be assigned * to the new token */ public TokenItem splitEnd(TokenItem token, int endLength, TokenID newTokenID, TokenContextPath newTokenContextPath) { if (!canSplitEnd(token, endLength)) { throw new IllegalStateException("Can't split the token=" + token); // NOI18N } String text = token.getImage(); if (endLength > text.length()) { throw new IllegalArgumentException("endLength=" + endLength // NOI18N + " is greater than token length=" + text.length()); // NOI18N } String newText = text.substring(0, endLength); ExtTokenItem newToken = (ExtTokenItem)insertToken(token.getNext(), newTokenID, newTokenContextPath, newText); // Update token-positions ftps.splitEndTokenPositions(token, endLength); remove(token, text.length() - endLength, endLength); return newToken; } /** Whether the token can be modified either by insertion or removal * at the given offset. */ public boolean canModifyToken(TokenItem token, int offset) { int wrLen = ((ExtTokenItem)token).getWrittenLength(); return (offset >= 0 && wrLen <= offset); } /** Insert the text at the offset inside the given token. * All the token-positions at and after the offset will * be increased by text.length(). * IllegalArgumentException is thrown if offset * is wrong. * @param token token in which the text is inserted. * @param offset offset at which the text will be inserted. * @param text text that will be inserted at the offset. */ public void insertString(TokenItem token, int offset, String text) { // Check debugging if (debugModify) { System.err.println("FormatWriter.insertString(): token=" + token // NOI18N + ", offset=" + offset + ", text='" + text + "'"); // NOI18N } // Check empty insert if (text.length() == 0) { return; } // Check whether modification is allowed if (!canModifyToken(token, offset)) { if (true) { // !!! return; } throw new IllegalStateException("Can't insert into token=" + token // NOI18N + ", at offset=" + offset + ", text='" + text + "'"); // NOI18N } // #5620 if (reformatting) { try { doc.insertString(getDocOffset(token) + offset, text, null); } catch (BadLocationException e) { e.printStackTrace(); } } // Update token-positions ftps.tokenTextInsert(token, offset, text.length()); String image = token.getImage(); ((ExtTokenItem)token).setImage(image.substring(0, offset) + text + image.substring(offset)); } /** Remove the length of the characters at the given * offset inside the given token. * IllegalArgumentException is thrown if offset * or length are wrong. * @param token token in which the text is removed. * @param offset offset at which the text will be removed. * @param length length of the removed text. */ public void remove(TokenItem token, int offset, int length) { // Check debugging if (debugModify) { String removedText; if (offset >= 0 && length >= 0 && offset + length <= token.getImage().length() ) { removedText = token.getImage().substring(offset, offset + length); } else { removedText = " |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.