![JAR search and dependency download from the Maven repository](/logo.png)
org.netbeans.editor.Formatter Maven / Gradle / Ivy
/*
* 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-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.io.IOException;
import java.io.Writer;
import java.io.CharArrayWriter;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.BadLocationException;
/**
* Various services related to indentation and text formatting
* are located here. Each kit can have different formatter
* so the first action should be getting the right formatter
* for the given kit by calling Formatter.getFormatter(kitClass).
*
* @author Miloslav Metelka
* @version 1.00
*/
public class Formatter implements SettingsChangeListener {
private static Map kitClass2Formatter = new HashMap();
/** Get the formatter for the given kit-class */
public static synchronized Formatter getFormatter(Class kitClass) {
Formatter f = (Formatter)kitClass2Formatter.get(kitClass);
if (f == null) {
f = BaseKit.getKit(kitClass).createFormatter();
kitClass2Formatter.put(kitClass, f);
}
return f;
}
/** Set the formatter for the given kit-class.
* @param kitClass class of the kit for which the formatter
* is being assigned.
* @param formatter new formatter for the given kit
*/
public static synchronized void setFormatter(Class kitClass, Formatter formatter) {
kitClass2Formatter.put(kitClass, formatter);
}
/** Maximum tab size for which the indent strings will be cached. */
private static final int ISC_MAX_TAB_SIZE = 16;
/** Cache the indentation strings up to this size */
private static final int ISC_MAX_INDENT_SIZE = 32;
/** Cache holding the indentation strings for various tab-sizes. */
private static final String[][] indentStringCache
= new String[ISC_MAX_TAB_SIZE][];
private final Class kitClass;
/** Whether values were already inited from the cache */
private boolean inited;
private int tabSize;
private boolean customTabSize;
private Integer shiftWidth;
private boolean customShiftWidth;
private boolean expandTabs;
private boolean customExpandTabs;
private int spacesPerTab;
private boolean customSpacesPerTab;
/** Construct new formatter.
* @param kitClass the class of the kit for which this formatter is being
* constructed.
*/
public Formatter(Class kitClass) {
this.kitClass = kitClass;
Settings.addSettingsChangeListener(this);
}
/** Get the kit-class for which this formatter is constructed. */
public Class getKitClass() {
return kitClass;
}
public void settingsChange(SettingsChangeEvent evt) {
String settingName = (evt != null) ? evt.getSettingName() : null;
if (!inited || settingName == null || SettingsNames.TAB_SIZE.equals(settingName)) {
if (!customTabSize) {
tabSize = SettingsUtil.getInteger(kitClass, SettingsNames.TAB_SIZE,
SettingsDefaults.defaultTabSize);
}
}
// Shift-width often depends on the rest of parameters
if (!customShiftWidth) {
Object shw = Settings.getValue(kitClass, SettingsNames.INDENT_SHIFT_WIDTH);
if (shw instanceof Integer) {
shiftWidth = (Integer)shw;
}
}
if (!inited || settingName == null || SettingsNames.EXPAND_TABS.equals(settingName)) {
if (!customExpandTabs) {
expandTabs = SettingsUtil.getBoolean(kitClass, SettingsNames.EXPAND_TABS,
SettingsDefaults.defaultExpandTabs);
}
}
if (!inited || settingName == null || SettingsNames.SPACES_PER_TAB.equals(settingName)) {
if (!customSpacesPerTab) {
spacesPerTab = SettingsUtil.getInteger(kitClass, SettingsNames.SPACES_PER_TAB,
SettingsDefaults.defaultSpacesPerTab);
}
}
inited = true;
}
/** Get the number of spaces the TAB character ('\t') visually represents
* for non-BaseDocument documents. It shouldn't be used for BaseDocument
* based documents. The reason for that is that the returned value
* reflects the value of the setting for the kit class over which
* this formatter was constructed. However it's possible that the kit class of
* the document being formatted is different than the kit of the formatter.
* For example java document could be formatted by html formatter.
* Therefore BaseDocument.getTabSize()
must be used
* for BaseDocuments to reflect the document's own tabsize.
* @see BaseDocument.getTabSize()
*/
public int getTabSize() {
if (!customTabSize && !inited) {
settingsChange(null);
}
return tabSize;
}
/** Set the number of spaces the TAB character ('\t') visually represents
* for non-BaseDocument documents. It doesn't affect BaseDocument
* based documents.
*
* @see getTabSize()
* @see BaseDocument.setTabSize()
*/
public void setTabSize(int tabSize) {
customTabSize = true;
this.tabSize = tabSize;
}
/** Get the width of one indentation level for non-BaseDocument documents.
* The algorithm first checks whether there's a value for the INDENT_SHIFT_WIDTH
* setting. If so it uses it, otherwise it uses getSpacesPerTab()
*
* @see setShiftWidth()
* @see getSpacesPerTab()
*/
public int getShiftWidth() {
if (!customShiftWidth && !inited) {
settingsChange(null);
}
return (shiftWidth != null) ? shiftWidth.intValue() : getSpacesPerTab();
}
/** Set the width of one indentation level for non-BaseDocument documents.
* It doesn't affect BaseDocument based documents.
*
* @see getShiftWidth()
*/
public void setShiftWidth(int shiftWidth) {
customShiftWidth = true;
if (this.shiftWidth == null || this.shiftWidth.intValue() != shiftWidth) {
this.shiftWidth = new Integer(shiftWidth);
}
}
/** Should the typed tabs be expanded to the spaces? */
public boolean expandTabs() {
if (!customExpandTabs && !inited) {
settingsChange(null);
}
return expandTabs;
}
public void setExpandTabs(boolean expandTabs) {
customExpandTabs = true;
this.expandTabs = expandTabs;
}
/** Get the number of spaces that should be inserted into the document
* instead of one typed tab.
*/
public int getSpacesPerTab() {
if (!customSpacesPerTab && !inited) {
settingsChange(null);
}
return spacesPerTab;
}
public void setSpacesPerTab(int spacesPerTab) {
customSpacesPerTab = true;
this.spacesPerTab = spacesPerTab;
}
static String getIndentString(int indent, boolean expandTabs, int tabSize) {
if (indent <= 0) {
return "";
}
if (expandTabs) { // store in 0th slot
tabSize = 0;
}
synchronized (Settings.class) {
boolean large = (tabSize >= indentStringCache.length)
|| (indent > ISC_MAX_INDENT_SIZE); // indexed by (indent - 1)
String indentString = null;
String[] tabCache = null;
if (!large) {
tabCache = indentStringCache[tabSize]; // cache for this tab
if (tabCache == null) {
tabCache = new String[ISC_MAX_INDENT_SIZE];
indentStringCache[tabSize] = tabCache;
}
indentString = tabCache[indent - 1];
}
if (indentString == null) {
indentString = Analyzer.getIndentString(indent, expandTabs, tabSize);
if (!large) {
tabCache[indent - 1] = indentString;
}
}
return indentString;
}
}
public String getIndentString(BaseDocument doc, int indent) {
return getIndentString(indent, expandTabs(), doc.getTabSize());
}
/** Get the string that is appropriate for the requested indentation.
* The returned string respects the expandTabs() and
* the getTabSize() and will contain either spaces only
* or fully or partially tabs as necessary.
*/
public String getIndentString(int indent) {
return getIndentString(indent, expandTabs(), getTabSize());
}
/** Modify the line to move the text starting at dotPos one tab
* column to the right. Whitespace preceeding dotPos may be
* replaced by a TAB character if tabs expanding is on.
* @param doc document to operate on
* @param dotPos insertion point
*/
public void insertTabString(BaseDocument doc, int dotPos)
throws BadLocationException {
doc.atomicLock();
try {
// Determine first white char before dotPos
int rsPos = Utilities.getRowStart(doc, dotPos);
int startPos = Utilities.getFirstNonWhiteBwd(doc, dotPos, rsPos);
startPos = (startPos >= 0) ? (startPos + 1) : rsPos;
int startCol = Utilities.getVisualColumn(doc, startPos);
int endCol = Utilities.getNextTabColumn(doc, dotPos);
String tabStr = Analyzer.getWhitespaceString(startCol, endCol, expandTabs(), doc.getTabSize());
// Search for the first non-common char
char[] removeChars = doc.getChars(startPos, dotPos - startPos);
int ind = 0;
while (ind < removeChars.length && removeChars[ind] == tabStr.charAt(ind)) {
ind++;
}
startPos += ind;
doc.remove(startPos, dotPos - startPos);
doc.insertString(startPos, tabStr.substring(ind), null);
} finally {
doc.atomicUnlock();
}
}
/** Change the indent of the given row. Document is atomically locked
* during this operation.
*/
public void changeRowIndent(BaseDocument doc, int pos, int newIndent)
throws BadLocationException {
doc.atomicLock();
try {
if (newIndent < 0) {
newIndent = 0;
}
int firstNW = Utilities.getRowFirstNonWhite(doc, pos);
if (firstNW == -1) { // valid first non-blank
firstNW = Utilities.getRowEnd(doc, pos);
}
int replacePos = Utilities.getRowStart(doc, pos);
int removeLen = firstNW - replacePos;
String removeText = doc.getText(replacePos, removeLen);
String newIndentText = getIndentString(doc, newIndent);
if (newIndentText.startsWith(removeText)) {
// Skip removeLen chars at start
newIndentText = newIndentText.substring(removeLen);
replacePos += removeLen;
removeLen = 0;
} else if (newIndentText.endsWith(removeText)) {
// Skip removeLen chars at the end
newIndentText = newIndentText.substring(0, newIndentText.length() - removeLen);
removeLen = 0;
}
if (removeLen != 0) {
doc.remove(replacePos, removeLen);
}
doc.insertString(replacePos, newIndentText, null);
} finally {
doc.atomicUnlock();
}
}
/** Increase/decrease indentation of the block of the code. Document
* is atomically locked during the operation.
* @param doc document to operate on
* @param startPos starting line position
* @param endPos ending line position
* @param shiftCnt positive/negative count of shiftwidths by which indentation
* should be shifted right/left
*/
public void changeBlockIndent(BaseDocument doc, int startPos, int endPos,
int shiftCnt) throws BadLocationException {
GuardedDocument gdoc = (doc instanceof GuardedDocument)
? (GuardedDocument)doc : null;
if (gdoc != null){
for (int i = startPos; i 0 && Utilities.getRowStart(doc, endPos) == endPos) {
endPos--;
}
int pos = Utilities.getRowStart(doc, startPos );
for (int lineCnt = Utilities.getRowCount(doc, startPos, endPos);
lineCnt > 0; lineCnt--
) {
int indent = Utilities.getRowIndent(doc, pos);
if (Utilities.isRowWhite(doc, pos)) {
indent = -indentDelta; // zero indentation for white line
}
changeRowIndent(doc, pos, Math.max(indent + indentDelta, 0));
pos = Utilities.getRowStart(doc, pos, +1);
}
} finally {
doc.atomicUnlock();
}
}
/** Shift line either left or right */
public void shiftLine(BaseDocument doc, int dotPos, boolean right)
throws BadLocationException {
int ind = doc.getShiftWidth();
if (!right) {
ind = -ind;
}
if (Utilities.isRowWhite(doc, dotPos)) {
ind += Utilities.getVisualColumn(doc, dotPos);
} else {
ind += Utilities.getRowIndent(doc, dotPos);
}
ind = Math.max(ind, 0);
changeRowIndent(doc, dotPos, ind);
}
/** Reformat a block of code.
* @param doc document to work with
* @param startOffset offset at which the formatting starts
* @param endOffset offset at which the formatting ends
* @return length of the reformatted code
*/
public int reformat(BaseDocument doc, int startOffset, int endOffset)
throws BadLocationException {
try {
CharArrayWriter cw = new CharArrayWriter();
Writer w = createWriter(doc, startOffset, cw);
w.write(doc.getChars(startOffset, endOffset - startOffset));
w.close();
String out = new String(cw.toCharArray());
doc.remove(startOffset, endOffset - startOffset);
doc.insertString(startOffset, out, null);
return out.length();
} catch (IOException e) {
Utilities.annotateLoggable(e);
return 0;
}
}
/** Indents the current line. Should not affect any other
* lines.
* @param doc the document to work on
* @param offset the offset of a character on the line
* @return new offset of the original character
*/
public int indentLine(Document doc, int offset) {
return offset;
}
/** Inserts new line at given position and indents the new line with
* spaces.
*
* @param doc the document to work on
* @param offset the offset of a character on the line
* @return new offset to place cursor to
*/
public int indentNewLine(Document doc, int offset) {
try {
doc.insertString(offset, "\n", null); // NOI18N
offset++;
} catch (GuardedException e) {
java.awt.Toolkit.getDefaultToolkit().beep();
} catch (BadLocationException e) {
Utilities.annotateLoggable(e);
}
return offset;
}
/** Creates a writer that formats text that is inserted into it.
* The writer should not modify the document but use the
* provided writer to write to. Usually the underlaying writer
* will modify the document itself and optionally it can remember
* the current position in document. That is why the newly created
* writer should do no buffering.
*
* The provided document and pos are only informational,
* should not be modified but only used to find correct indentation
* strategy.
*
* @param doc document
* @param offset position to begin inserts at
* @param writer writer to write to
* @return new writer that will format written text and pass it
* into the writer
*/
public Writer createWriter(Document doc, int offset, Writer writer) {
return writer;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy