All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.editor.Utilities 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.awt.Rectangle;
import java.awt.Frame;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.Writer;
import javax.swing.SwingUtilities;
import javax.swing.Action;
import javax.swing.KeyStroke;
import javax.swing.text.JTextComponent;
import javax.swing.text.BadLocationException;
import javax.swing.text.AttributeSet;
import javax.swing.text.EditorKit;
import javax.swing.text.Document;
import javax.swing.text.TextAction;
import javax.swing.text.Caret;
import javax.swing.plaf.TextUI;
import javax.swing.text.Element;
import javax.swing.text.View;
import org.openide.ErrorManager;

/**
* Various useful editor functions. Some of the methods have
* the same names and signatures like in javax.swing.Utilities but
* there is also many other useful methods.
* All the methods are static so there's no reason to instantiate Utilities.
*
* All the methods working with the document rely on that it is locked against
* modification so they don't acquire document read/write lock by themselves
* to guarantee the full thread safety of the execution.
* It's the user's task to lock the document appropriately
* before using methods described here.
*
* Most of the methods require org.netbeans.editor.BaseDocument instance
* not just the javax.swing.text.Document.
* The reason for that is to mark that the methods work on BaseDocument
* instances only, not on generic documents. To convert the Document
* to BaseDocument the simple conversion (BaseDocument)target.getDocument()
* can be done or the method getDocument(target) can be called.
* There are also other conversion methods like getEditorUI(), getKit()
* or getKitClass().
*
* @author Miloslav Metelka
* @version 0.10
*/

public class Utilities {

    private static final String WRONG_POSITION_LOCALE = "wrong_position"; // NOI18N

    /** Switch the case to capital letters. Used in changeCase() */
    public static final int CASE_UPPER = 0;

    /** Switch the case to small letters. Used in changeCase() */
    public static final int CASE_LOWER = 1;

    /** Switch the case to reverse. Used in changeCase() */
    public static final int CASE_SWITCH = 2;
    
    /** Fake TextAction for getting the info of the focused component */
    private static TextAction focusedComponentAction;    
    
    private static final boolean isLoggable = ErrorManager.getDefault().isLoggable(ErrorManager.INFORMATIONAL);

    private Utilities() {
        // instantiation has no sense
    }

    /** Get the starting position of the row.
    * @param c text component to operate on
    * @param offset position in document where to start searching
    * @return position of the start of the row or -1 for invalid position
    */
    public static int getRowStart(JTextComponent c, int offset)
    throws BadLocationException {
        return javax.swing.text.Utilities.getRowStart(c, offset);
        //return getRowStart((BaseDocument)c.getDocument(), offset, 0);
    }

    /** Get the starting position of the row.
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @return position of the start of the row or -1 for invalid position
    */
    public static int getRowStart(BaseDocument doc, int offset)
    throws BadLocationException {
        return getRowStart(doc, offset, 0);
    }

    /** Get the starting position of the row while providing relative count
    * of row how the given position should be shifted. This is the most
    * efficient way how to move by lines in the document based on some
    * position. There is no similair getRowEnd() method that would have
    * shifting parameter.
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @param lineShift shift the given offset forward/back relatively
    *  by some amount of lines
    * @return position of the start of the row or -1 for invalid position
    */
    public static int getRowStart(BaseDocument doc, int offset, int lineShift)
    throws BadLocationException {
        
        checkOffsetValid(doc, offset);

        if (lineShift != 0) {
            Element lineRoot = doc.getParagraphElement(0).getParentElement();
            int line = lineRoot.getElementIndex(offset);
            line += lineShift;
            if (line < 0 || line >= lineRoot.getElementCount()) {
                return -1; // invalid line shift
            }
            return lineRoot.getElement(line).getStartOffset();

        } else { // no shift
            return doc.getParagraphElement(offset).getStartOffset();
        }
    }

    /** Get the first non-white character on the line.
    * The document.isWhitespace() is used to test whether the particular
    * character is white space or not.
    * @param doc document to operate on
    * @param offset position in document anywhere on the line
    * @return position of the first non-white char on the line or -1
    *   if there's no non-white character on that line.
    */
    public static int getRowFirstNonWhite(BaseDocument doc, int offset)
    throws BadLocationException {
        
        checkOffsetValid(doc, offset);

        Element lineElement = doc.getParagraphElement(offset);
        return getFirstNonWhiteFwd(doc,
            lineElement.getStartOffset(),
            lineElement.getEndOffset() - 1
        );
    }

    /** Get the last non-white character on the line.
    * The document.isWhitespace() is used to test whether the particular
    * character is white space or not.
    * @param doc document to operate on
    * @param offset position in document anywhere on the line
    * @return position of the last non-white char on the line or -1
    *   if there's no non-white character on that line.
    */
    public static int getRowLastNonWhite(BaseDocument doc, int offset)
    throws BadLocationException {
        
        checkOffsetValid(doc, offset);

        Element lineElement = doc.getParagraphElement(offset);
        return getFirstNonWhiteBwd(doc,
            lineElement.getEndOffset() - 1,
            lineElement.getStartOffset()
        );
    }

    /** Get indentation on the current line. If this line is white then
    * return -1.
    * @param doc document to operate on
    * @param offset position in document anywhere on the line
    * @return indentation or -1 if the line is white
    */
    public static int getRowIndent(BaseDocument doc, int offset)
    throws BadLocationException {
        offset = getRowFirstNonWhite(doc, offset);
        if (offset == -1) {
            return -1;
        }
        return doc.getVisColFromPos(offset);
    }

    /** Get indentation on the current line. If this line is white then
    * go either up or down an return indentation of the first non-white row.
    * The getRowFirstNonWhite() is used to find the indentation
    * on particular line.
    * @param doc document to operate on
    * @param offset position in document anywhere on the line
    * @param downDir if this flag is set to true then if the row is white
    *   then the indentation of the next first non-white row is returned. If it's
    *   false then the indentation of the previous first non-white row is returned.
    * @return indentation or -1 if there's no non-white line in the specified direction
    */
    public static int getRowIndent(BaseDocument doc, int offset, boolean downDir)
    throws BadLocationException {
        int p = getRowFirstNonWhite(doc, offset);
        if (p == -1) {
            p = getFirstNonWhiteRow(doc, offset, downDir);
            if (p == -1) {
                return -1; // non-white line not found
            }
            p = getRowFirstNonWhite(doc, p);
            if (p == -1) {
                return -1; // non-white line not found
            }
        }
        return doc.getVisColFromPos(p);
    }

    /** Get the end position of the row right before the new-line character.
    * @param c text component to operate on
    * @param offset position in document where to start searching
    * @param relLine shift offset forward/back by some amount of lines
    * @return position of the end of the row or -1 for invalid position
    */
    public static int getRowEnd(JTextComponent c, int offset)
    throws BadLocationException {
        return javax.swing.text.Utilities.getRowEnd(c, offset);
        //return getRowEnd((BaseDocument)c.getDocument(), offset);
    }

    public static int getRowEnd(BaseDocument doc, int offset)
    throws BadLocationException {
        checkOffsetValid(doc, offset);

        return doc.getParagraphElement(offset).getEndOffset() - 1;
    }

    /** Get the position that is one line above and visually at some
    * x-coordinate value.
    * @param doc document to operate on
    * @param offset position in document from which the current line is determined
    * @param x float x-coordinate value
    * @return position of the character that is at the one line above at
    *   the required x-coordinate value
    */
    public static int getPositionAbove(JTextComponent c, int offset, int x)
    throws BadLocationException {
        int offs = offset;
	int lastOffs = getRowStart(c, offs) - 1;
	if (lastOffs < 0) {
	    return -1;
	}
	int bestSpan = Short.MAX_VALUE;
	int y = 0;
        int width = 0;
	Rectangle r = null;
	if (lastOffs >= 0) {
	    r = c.modelToView(lastOffs);
	    y = r.y;
            width = r.width;
	}
        
        Rectangle lastR = r;
        int span = -1;
	while ((r != null) && (y == r.y) && r.x >= x) {
	    span = Math.abs(r.x - x);
	    if (span < bestSpan) {
		offs = lastOffs;
		bestSpan = span;
	    }
	    lastOffs -= 1;
            lastR = r;            
	    r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null;
	}

        if (r!=null && y == r.y && span>width){
            offs = c.viewToModel(new java.awt.Point(r.x, r.y));
        } else {
            offs = c.viewToModel(new java.awt.Point(lastR.x, lastR.y));
        }
        
        return offs;

//        return javax.swing.text.Utilities.getPositionAbove(c, offset, x);
    }

    /** Get the position that is one line above and visually at some
    * x-coordinate value.
    * @param c text component to operate on
    * @param offset position in document from which the current line is determined
    * @param x float x-coordinate value
    * @return position of the character that is at the one line above at
    *   the required x-coordinate value
    */
    public static int getPositionBelow(JTextComponent c, int offset, int x)
    throws BadLocationException {
        return javax.swing.text.Utilities.getPositionBelow(c, offset, x);
        /*
        BaseDocument doc = (BaseDocument)c.getDocument();
        BaseTextUI ui = (BaseTextUI)c.getUI();
        offset = ui.viewToModel(c, x, ui.getYFromPos(offset) + ui.getEditorUI().getLineHeight());
        return offset;
        */
    }

    /** Get start of the current word. If there are no more words till
    * the begining of the document, this method returns -1.
    * @param c text component to operate on
    * @param offset position in document from which the current line is determined
    */
    public static int getWordStart(JTextComponent c, int offset)
    throws BadLocationException {
        return getWordStart((BaseDocument)c.getDocument(), offset);
    }

    public static int getWordStart(BaseDocument doc, int offset)
    throws BadLocationException {
        return doc.find(new FinderFactory.PreviousWordBwdFinder(doc, false, true),
                        offset, 0);
    }

    public static int getWordEnd(JTextComponent c, int offset)
    throws BadLocationException {
        return getWordEnd((BaseDocument)c.getDocument(), offset);
    }

    public static int getWordEnd(BaseDocument doc, int offset)
    throws BadLocationException {
        int ret = doc.find(new FinderFactory.NextWordFwdFinder(doc, false, true),
                        offset, -1);
        return (ret > 0) ? ret : doc.getLength();
    }

    public static int getNextWord(JTextComponent c, int offset)
    throws BadLocationException {
        int nextWordOffset = getNextWord((BaseDocument)c.getDocument(), offset);
        int nextVisualPosition = c.getUI().getNextVisualPositionFrom(c,
                              nextWordOffset, null, javax.swing.SwingConstants.EAST, null);
        return (nextVisualPosition - 1 == nextWordOffset) ?  nextWordOffset : nextVisualPosition - 1;
    }

    public static int getNextWord(BaseDocument doc, int offset)
    throws BadLocationException {
        Finder nextWordFinder = (Finder)doc.getProperty(SettingsNames.NEXT_WORD_FINDER);
        offset = doc.find(nextWordFinder, offset, -1);
        if (offset < 0) {
            offset = doc.getLength();
        }
        return offset;
    }

    public static int getPreviousWord(JTextComponent c, int offset)
    throws BadLocationException {
        int prevWordOffset = getPreviousWord((BaseDocument)c.getDocument(), offset);
        int nextVisualPosition = c.getUI().getNextVisualPositionFrom(c,
                              prevWordOffset, null, javax.swing.SwingConstants.WEST, null);
        return (nextVisualPosition + 1 == prevWordOffset) ?  prevWordOffset : nextVisualPosition + 1;
    }

    public static int getPreviousWord(BaseDocument doc, int offset)
    throws BadLocationException {
        Finder prevWordFinder = (Finder)doc.getProperty(SettingsNames.PREVIOUS_WORD_FINDER);
        offset = doc.find(prevWordFinder, offset, 0);
        if (offset < 0) {
            offset = 0;
        }
        return offset;
    }

    /** Get first white character in document in forward direction
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @return position of the first white character or -1
    */
    public static int getFirstWhiteFwd(BaseDocument doc, int offset)
    throws BadLocationException {
        return getFirstWhiteFwd(doc, offset, -1);
    }

    /** Get first white character in document in forward direction
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @param limitPos position in document (greater or equal than offset) where
    *   the search will stop reporting unsuccessful search by returning -1
    * @return position of the first non-white character or -1
    */
    public static int getFirstWhiteFwd(BaseDocument doc, int offset, int limitPos)
    throws BadLocationException {
        return doc.find(new FinderFactory.WhiteFwdFinder(doc), offset, limitPos);
    }

    /** Get first non-white character in document in forward direction
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @return position of the first non-white character or -1
    */
    public static int getFirstNonWhiteFwd(BaseDocument doc, int offset)
    throws BadLocationException {
        return getFirstNonWhiteFwd(doc, offset, -1);
    }

    /** Get first non-white character in document in forward direction
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @param limitPos position in document (greater or equal than offset) where
    *   the search will stop reporting unsuccessful search by returning -1
    * @return position of the first non-white character or -1
    */
    public static int getFirstNonWhiteFwd(BaseDocument doc, int offset, int limitPos)
    throws BadLocationException {
        return doc.find(new FinderFactory.NonWhiteFwdFinder(doc), offset, limitPos);
    }

    /** Get first white character in document in backward direction.
    * The character right before the character at position offset will
    * be searched as first.
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @return position of the first white character or -1
    */
    public static int getFirstWhiteBwd(BaseDocument doc, int offset)
    throws BadLocationException {
        return getFirstWhiteBwd(doc, offset, 0);
    }

    /** Get first white character in document in backward direction.
    * The character right before the character at position offset will
    * be searched as first.
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @param limitPos position in document (lower or equal than offset) where
    *   the search will stop reporting unsuccessful search by returning -1
    * @return position of the first white character or -1
    */
    public static int getFirstWhiteBwd(BaseDocument doc, int offset, int limitPos)
    throws BadLocationException {
        return doc.find(new FinderFactory.WhiteBwdFinder(doc), offset, limitPos);
    }

    /** Get first non-white character in document in backward direction.
    * The character right before the character at position offset will
    * be searched as first.
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @return position of the first non-white character or -1
    */
    public static int getFirstNonWhiteBwd(BaseDocument doc, int offset)
    throws BadLocationException {
        return getFirstNonWhiteBwd(doc, offset, 0);
    }

    /** Get first non-white character in document in backward direction.
    * The character right before the character at position offset will
    * be searched as first.
    * @param doc document to operate on
    * @param offset position in document where to start searching
    * @param limitPos position in document (lower or equal than offset) where
    *   the search will stop reporting unsuccessful search by returning -1
    * @return position of the first non-white character or -1
    */
    public static int getFirstNonWhiteBwd(BaseDocument doc, int offset, int limitPos)
    throws BadLocationException {
        return doc.find(new FinderFactory.NonWhiteBwdFinder(doc), offset, limitPos);
    }

    /** Return line offset (line number - 1) for some position in the document
    * @param doc document to operate on
    * @param offset position in document where to start searching
    */
    public static int getLineOffset(BaseDocument doc, int offset)
    throws BadLocationException {
        
        checkOffsetValid(offset, doc.getLength() + 1);

        Element lineRoot = doc.getParagraphElement(0).getParentElement();
        return lineRoot.getElementIndex(offset);
    }

    /** Return start offset of the line
    * @param lineIndex line index starting from 0
    * @return start position of the line or -1 if lineIndex was invalid
    */
    public static int getRowStartFromLineOffset(BaseDocument doc, int lineIndex) {
        Element lineRoot = doc.getParagraphElement(0).getParentElement();
        if (lineIndex < 0 || lineIndex >= lineRoot.getElementCount()) {
            return -1; // invalid line number

        } else {
            return lineRoot.getElement(lineIndex).getStartOffset();
        }
    }

    /** Return visual column (with expanded tabs) on the line.
    * @param doc document to operate on
    * @param offset position in document for which the visual column should be found
    * @return visual column on the line determined by position
    */
    public static int getVisualColumn(BaseDocument doc, int offset)
    throws BadLocationException {
        
        int docLen = doc.getLength();
        if (offset == docLen + 1) { // at ending extra '\n' => make docLen to proceed without BLE
            offset = docLen;
        }

        return doc.getVisColFromPos(offset);
    }

    /** Get the identifier around the given position or null if there's no identifier
    * @see getIdentifierBlock()
    */
    public static String getIdentifier(BaseDocument doc, int offset)
    throws BadLocationException {
        int[] blk = getIdentifierBlock(doc, offset);
        return (blk != null) ? doc.getText(blk[0], blk[1] - blk[0]) : null;
    }


    /** Get the identifier around the given position or null if there's no identifier
     * around the given position. The identifier is not verified against SyntaxSupport.isIdentifier().
     * @param c JTextComponent to work on
     * @param offset position in document - usually the caret.getDot()
     * @return the block (starting and ending position) enclosing the identifier
     * or null if no identifier was found
     */
    public static int[] getIdentifierBlock(JTextComponent c, int offset)
    throws BadLocationException {
        String id = null;
        int[] ret = null;
        Document doc = c.getDocument();
        int idStart = javax.swing.text.Utilities.getWordStart(c, offset);
        if (idStart >= 0) {
            int idEnd = javax.swing.text.Utilities.getWordEnd(c, idStart);
            if (idEnd >= 0) {
                id = doc.getText(idStart, idEnd - idStart);
                ret = new int[] { idStart, idEnd };
                String trim = id.trim();
                if (trim.length() == 0 || (trim.length() == 1 && !Character.isJavaIdentifierPart(trim.charAt(0)))) {
                    int prevWordStart = javax.swing.text.Utilities.getPreviousWord(c, offset);
                    if (offset == javax.swing.text.Utilities.getWordEnd(c,prevWordStart )){
                        ret = new int[] { prevWordStart, offset };
                    }
                } else if ((id != null) && (id.length() != 0)  && (id.indexOf(".") != -1)){ //NOI18N
                    int index = offset - idStart;
                    int begin = id.substring(0, index).lastIndexOf("."); //NOI18N
                    begin = (begin == -1) ? 0 : begin + 1; //first index after the dot, if exists
                    int end = id.indexOf(".", index); //NOI18N
                    end = (end == -1) ? id.length() : end;
                    ret = new int[] { idStart+begin, idStart+end };
                }
            }
        }
        return ret;
    }
    
    
    
    /** Get the identifier around the given position or null if there's no identifier
    * around the given position. The identifier must be
    * accepted by SyntaxSupport.isIdnetifier() otherwise null is returned.
    * @param doc document to work on
    * @param offset position in document - usually the caret.getDot()
    * @return the block (starting and ending position) enclosing the identifier
    *   or null if no identifier was found
    */
    public static int[] getIdentifierBlock(BaseDocument doc, int offset)
    throws BadLocationException {
        int[] ret = null;
        int idStart = getWordStart(doc, offset);
        if (idStart >= 0) {
            int idEnd = getWordEnd(doc, idStart);
            if (idEnd >= 0) {
                String id = doc.getText(idStart, idEnd - idStart);
                if (doc.getSyntaxSupport().isIdentifier(id)) {
                    ret = new int[] { idStart, idEnd };
                } else { // not identifier by syntax support
                    id = getWord(doc, offset); // try right at offset
                    if (doc.getSyntaxSupport().isIdentifier(id)) {
                        ret = new int[] { offset, offset + id.length() };
                    }
                }
            }
        }
        return ret;
    }

    
    /** Get the word around the given position .
     * @param c component to work with
     * @param offset position in document - usually the caret.getDot()
     * @return the word.
     */
    public static String getWord(JTextComponent c, int offset)
    throws BadLocationException {
        int[] blk = getIdentifierBlock(c, offset);
        Document doc = c.getDocument();
        return (blk != null) ? doc.getText(blk[0], blk[1] - blk[0]) : null;
    }
    
    
    /** Get the selection if there's any or get the identifier around
    * the position if there's no selection.
    * @param c component to work with
    * @param offset position in document - usually the caret.getDot()
    * @return the block (starting and ending position) enclosing the identifier
    *   or null if no identifier was found
    */
    public static int[] getSelectionOrIdentifierBlock(JTextComponent c, int offset)
    throws BadLocationException {
        Document doc = c.getDocument();
        Caret caret = c.getCaret();
        int[] ret;
        if (caret.isSelectionVisible()) {
            ret = new int[] { c.getSelectionStart(), c.getSelectionEnd() }; 
        } else if (doc instanceof BaseDocument){
            ret = getIdentifierBlock((BaseDocument)doc, caret.getDot());
        } else {
            ret = getIdentifierBlock(c, offset);
        }
        return ret;
    }

    /** Get the selection or identifier at the current caret position
     * @see getSelectionOrIdentifierBlock(JTextComponent, int)
     */
    public static int[] getSelectionOrIdentifierBlock(JTextComponent c) {
        try {
            return getSelectionOrIdentifierBlock(c, c.getCaret().getDot());
        } catch (BadLocationException e) {
            return null;
        }
    }

    /** Get the identifier before the given position (ending at given offset)
    * or null if there's no identifier
    */
    public static String getIdentifierBefore(BaseDocument doc, int offset)
    throws BadLocationException {
        int wordStart = getWordStart(doc, offset);
        if (wordStart != -1) {
            String word = new String(doc.getChars(wordStart,
                                                  offset - wordStart), 0, offset - wordStart);
            if (doc.getSyntaxSupport().isIdentifier(word)) {
                return word;
            }
        }
        return null;
    }

    /** Get the selection if there's any or get the identifier around
    * the position if there's no selection.
    */
    public static String getSelectionOrIdentifier(JTextComponent c, int offset)
    throws BadLocationException {
        Document doc = c.getDocument();
        Caret caret = c.getCaret();
        String ret;
        if (caret.isSelectionVisible()) {
            ret = c.getSelectedText();
	    if (ret != null) return ret;
        } 
	if (doc instanceof BaseDocument){
	    ret = getIdentifier((BaseDocument) doc, caret.getDot());
        } else {
	    ret = getWord(c, offset);
	}
        return ret;
    }

    /** Get the selection or identifier at the current caret position */
    public static String getSelectionOrIdentifier(JTextComponent c) {
        try {
            return getSelectionOrIdentifier(c, c.getCaret().getDot());
        } catch (BadLocationException e) {
            return null;
        }
    }

    /** Get the word at given position.
    */
    public static String getWord(BaseDocument doc, int offset)
    throws BadLocationException {
        int wordEnd = getWordEnd(doc, offset);
        if (wordEnd != -1) {
            return new String(doc.getChars(offset, wordEnd - offset), 0,
                              wordEnd - offset);
        }
        return null;
    }


    /** Change the case for specified part of document
    * @param doc document to operate on
    * @param offset position in document determines the changed area begining
    * @param len number of chars to change
    * @param type either CASE_CAPITAL, CASE_SMALL or CASE_SWITCH
    */
    public static boolean changeCase(BaseDocument doc, int offset, int len, int type)
    throws BadLocationException {
        char[] orig = doc.getChars(offset, len);
        char[] changed = (char[])orig.clone();
        for (int i = 0; i < orig.length; i++) {
            switch (type) {
            case CASE_UPPER:
                changed[i] = Character.toUpperCase(orig[i]);
                break;
            case CASE_LOWER:
                changed[i] = Character.toLowerCase(orig[i]);
                break;
            case CASE_SWITCH:
                if (Character.isUpperCase(orig[i])) {
                    changed[i] = Character.toLowerCase(orig[i]);
                } else if (Character.isLowerCase(orig[i])) {
                    changed[i] = Character.toUpperCase(orig[i]);
                }
                break;
            }
        }
        // check chars for difference and possibly change document
        for (int i = 0; i < orig.length; i++) {
            if (orig[i] != changed[i]) {
                doc.atomicLock();
                try {
                    doc.remove(offset, orig.length);
                    doc.insertString(offset, new String(changed), null);
                } finally {
                    doc.atomicUnlock();
                }
                return true; // changed
            }
        }
        return false;
    }

    /** Tests whether the line contains no characters except the ending new-line.
    * @param doc document to operate on
    * @param offset position anywhere on the tested line
    * @return whether the line is empty or not
    */
    public static boolean isRowEmpty(BaseDocument doc, int offset)
    throws BadLocationException {
        Element lineElement = doc.getParagraphElement(offset);
        return (lineElement.getStartOffset() + 1 == lineElement.getEndOffset());
    }

    public static int getFirstNonEmptyRow(BaseDocument doc, int offset, boolean downDir)
    throws BadLocationException {
        while (offset != -1 && isRowEmpty(doc, offset)) {
            offset = getRowStart(doc, offset, downDir ? +1 : -1);
        }
        return offset;
    }

    /** Tests whether the line contains only whitespace characters.
    * @param doc document to operate on
    * @param offset position anywhere on the tested line
    * @return whether the line is empty or not
    */
    public static boolean isRowWhite(BaseDocument doc, int offset)
    throws BadLocationException {
        Element lineElement = doc.getParagraphElement(offset);
        offset = doc.find(new FinderFactory.NonWhiteFwdFinder(doc),
              lineElement.getStartOffset(), lineElement.getEndOffset() - 1);
        return (offset == -1);
    }

    public static int getFirstNonWhiteRow(BaseDocument doc, int offset, boolean downDir)
    throws BadLocationException {
        if (isRowWhite(doc, offset)) {
            if (downDir) { // search down for non-white line
                offset = getFirstNonWhiteFwd(doc, offset);
            } else { // search up for non-white line
                offset = getFirstNonWhiteBwd(doc, offset);
            }
        }
        return offset;
    }

    /** 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 static int reformat(BaseDocument doc, int startOffset, int endOffset)
    throws BadLocationException {
        return doc.getFormatter().reformat(doc, startOffset, endOffset);
    }

    /** Reformat the line around the given position. */
    public static void reformatLine(BaseDocument doc, int pos)
    throws BadLocationException {
        int lineStart = getRowStart(doc, pos);
        int lineEnd = getRowEnd(doc, pos);
        reformat(doc, lineStart, lineEnd);
    }

    /** Count of rows between these two positions */
    public static int getRowCount(BaseDocument doc, int startPos, int endPos)
    throws BadLocationException {
        if (startPos > endPos) {
            return 0;
        }
        Element lineRoot = doc.getParagraphElement(0).getParentElement();
        return lineRoot.getElementIndex(endPos) - lineRoot.getElementIndex(startPos) + 1;
    }

    /** Get the total count of lines in the document */
    public static int getRowCount(BaseDocument doc) {
        return doc.getParagraphElement(0).getParentElement().getElementCount();
    }

    /** @deprecated
     * @see Formatter.insertTabString()
     */
    public static String getTabInsertString(BaseDocument doc, int offset)
    throws BadLocationException {
        int col = getVisualColumn(doc, offset);
        Formatter f = doc.getFormatter();
        boolean expandTabs = f.expandTabs();
        if (expandTabs) {
            int spacesPerTab = f.getSpacesPerTab();
            int len = (col + spacesPerTab) / spacesPerTab * spacesPerTab - col;
            return new String(Analyzer.getSpacesBuffer(len), 0, len);
        } else { // insert pure tab
            return "\t"; // NOI18N
        }
    }

    /** Get the visual column corresponding to the position after pressing
     * the TAB key.
     * @param doc document to work with
     * @param offset position at which the TAB was pressed
     */
    public static int getNextTabColumn(BaseDocument doc, int offset)
    throws BadLocationException {
        int col = getVisualColumn(doc, offset);
        int tabSize = doc.getFormatter().getSpacesPerTab();
        return (col + tabSize) / tabSize * tabSize;
    }

    public static void setStatusText(JTextComponent c, String text) {
        StatusBar sb = getEditorUI(c).getStatusBar();
        if (sb != null) {
            sb.setText(StatusBar.CELL_MAIN, text);
        }
    }

    public static void setStatusText(JTextComponent c, String text,
                                     Coloring extraColoring) {
        StatusBar sb = getEditorUI(c).getStatusBar();
        if (sb != null) {
            sb.setText(StatusBar.CELL_MAIN, text, extraColoring);
        }
    }

    public static void setStatusBoldText(JTextComponent c, String text) {
        StatusBar sb = getEditorUI(c).getStatusBar();
        if (sb != null) {
            sb.setBoldText(StatusBar.CELL_MAIN, text);
        }
    }

    public static String getStatusText(JTextComponent c) {
        StatusBar sb = getEditorUI(c).getStatusBar();
        return (sb != null) ? sb.getText(StatusBar.CELL_MAIN) : null;
    }

    public static void clearStatusText(JTextComponent c) {
        setStatusText(c, ""); // NOI18N
    }

    public static void insertMark(BaseDocument doc, Mark mark, int offset)
    throws BadLocationException, InvalidMarkException {
        mark.insert(doc, offset);
    }

    public static void moveMark(BaseDocument doc, Mark mark, int newOffset)
    throws BadLocationException, InvalidMarkException {
        mark.move(doc, newOffset);
    }

    public static void returnFocus() {
         JTextComponent c = getLastActiveComponent();
         if (c != null) {
             requestFocus(c);
         }
    }

    public static void requestFocus(JTextComponent c) {
        if (c != null) {
            if (!ImplementationProvider.getDefault().activateComponent(c)) {
                Frame f = EditorUI.getParentFrame(c);
                if (f != null) {
                    f.requestFocus();
                }
                c.requestFocus();
            }
        }
    }

    public static void runInEventDispatchThread(Runnable r) {
        if (SwingUtilities.isEventDispatchThread()) {
            r.run();
        } else {
            SwingUtilities.invokeLater(r);
        }
    }

    public static String debugPosition(BaseDocument doc, int offset) {
        String ret;

        if (offset >= 0) {
            try {
                int line = getLineOffset(doc, offset) + 1;
                int col = getVisualColumn(doc, offset) + 1;
                ret = String.valueOf(line) + ":" + String.valueOf(col); // NOI18N
            } catch (BadLocationException e) {
                ret = LocaleSupport.getString( WRONG_POSITION_LOCALE )
                      + ' ' + offset + " > " + doc.getLength(); // NOI18N
            }
        } else {
            ret = String.valueOf(offset);
        }

        return ret;
    }
    
    public static String offsetToLineColumnString(BaseDocument doc, int offset) {
        return String.valueOf(offset) + "[" + debugPosition(doc, offset) + "]"; // NOI18N
    }

    /** Display the identity of the document together with the title property
     * and stream-description property.
     */
    public static String debugDocument(Document doc) {
        return "<" + System.identityHashCode(doc) // NOI18N
            + ", title='" + doc.getProperty(Document.TitleProperty)
            + "', stream='" + doc.getProperty(Document.StreamDescriptionProperty)
            + ", " + doc.toString() + ">"; // NOI18N
    }

    public static void performAction(Action a, ActionEvent evt, JTextComponent target) {
        if (a instanceof BaseAction) {
            ((BaseAction)a).actionPerformed(evt, target);
        } else {
            a.actionPerformed(evt);
        }
    }

    /** Returns last activated component. If the component was closed, 
     *  then previous component is returned */
    public static JTextComponent getLastActiveComponent() {
        return Registry.getMostActiveComponent();
    }
    
    /**
     * Fetches the text component that currently has focus. It delegates to 
     * TextAction.getFocusedComponent().
     * @return the component
     */
    public static JTextComponent getFocusedComponent() {
        /** Fake action for getting the focused component */
        class FocusedComponentAction extends TextAction {
            
            FocusedComponentAction() {
                super("focused-component"); // NOI18N
            }
            
            /** adding this method because of protected final getFocusedComponent */
            JTextComponent getFocusedComponent2() {
                return getFocusedComponent();
            }
            
            public void actionPerformed(ActionEvent evt){}
        };
        
        if (focusedComponentAction == null) {
            focusedComponentAction = new FocusedComponentAction();
        }
        
        return ((FocusedComponentAction)focusedComponentAction).getFocusedComponent2();
    }

    /** Helper method to obtain instance of EditorUI (extended UI)
     * from the existing JTextComponent.
     * It doesn't require any document locking.
     * @param target JTextComponent for which the extended UI should be obtained
     * @return extended ui instance or null if the component.getUI()
     *   does not return BaseTextUI instance.
     */
    public static EditorUI getEditorUI(JTextComponent target) {
        TextUI ui = target.getUI();
        return (ui instanceof BaseTextUI) 
            ? ((BaseTextUI)ui).getEditorUI()
            : null;
    }

    /** Helper method to obtain instance of editor kit from existing JTextComponent.
    * If the kit of the component is not an instance
    * of the org.netbeans.editor.BaseKit the method returns null.
    * The method doesn't require any document locking.
    * @param target JTextComponent for which the editor kit should be obtained
    * @return BaseKit instance or null
    */
    public static BaseKit getKit(JTextComponent target) {
        if (target == null) return null; // #19574
        EditorKit ekit = target.getUI().getEditorKit(target);
        return (ekit instanceof BaseKit) ? (BaseKit)ekit : null;
    }

    /** Helper method to obtain editor kit class from existing JTextComponent.
    * This method is useful for example when dealing with Settings.
    * The method doesn't require any document locking.
    * @param target JTextComponent for which the editor kit should be obtained
    * @return editor kit class
    */
    public static Class getKitClass(JTextComponent target) {
        return (target != null) ? target.getUI().getEditorKit(target).getClass() : null;
    }

    /** Helper method to obtain instance of BaseDocument from JTextComponent.
    * If the document of the component is not an instance
    * of the org.netbeans.editor.BaseDocument the method returns null.
    * The method doesn't require any document locking.
    * @param target JTextComponent for which the document should be obtained
    * @return BaseDocument instance or null
    */
    public static BaseDocument getDocument(JTextComponent target) {
        Document doc = target.getDocument();
        return (doc instanceof BaseDocument) ? (BaseDocument)doc : null;
    }

    /** Get the syntax-support class that belongs to the document of the given
    * component. Besides using directly this method, the SyntaxSupport
    * can be obtained by calling doc.getSyntaxSupport().
    * The method can return null in case the document is not
    * an instance of the BaseDocument.
    * The method doesn't require any document locking.
    * @param target JTextComponent for which the syntax-support should be obtained
    * @return SyntaxSupport instance or null
    */
    public static SyntaxSupport getSyntaxSupport(JTextComponent target) {
        Document doc = target.getDocument();
        return (doc instanceof BaseDocument) ? ((BaseDocument)doc).getSyntaxSupport() : null;
    }

    /**
     * Get first view in the hierarchy that is an instance of the given class.
     * It allows to skip various wrapper-views around the doc-view that holds
     * the child views for the lines.
     *
     * @param component component from which the root view is fetched.
     * @param rootViewClass class of the view to return.
     * @return view being instance of the requested class or null if there
     *  is not one.
     */
    public static View getRootView(JTextComponent component, Class rootViewClass) {
        View view = null;
        TextUI textUI = (TextUI)component.getUI();
        if (textUI != null) {
            view = textUI.getRootView(component);
            while (view != null && !rootViewClass.isInstance(view)
                && view.getViewCount() == 1 // must be wrapper view
            ) {
                view = view.getView(0); // get the only child
            }
        }
        
        return view;
    }
    
    /**
     * Get the view that covers the whole area of the document
     * and holds a child view for each line in the document
     * (or for a bunch of lines in case there is a code folding present).
     */
    public static View getDocumentView(JTextComponent component) {
        return getRootView(component, DrawEngineDocView.class);
    }

    /**
     * Creates nice textual description of sequence of KeyStrokes. Usable for
     * displaying MultiKeyBindings. The keyStrokes are delimited by space.
     * @param Array of KeyStrokes representing the actual sequence.
     * @return String describing the KeyStroke sequence.
     */
    public static String keySequenceToString( KeyStroke[] seq ) {
        StringBuffer sb = new StringBuffer();
        for( int i=0; i0 ) sb.append( ' ' );  // NOI18N
            sb.append( keyStrokeToString( seq[i] ) );
        }
        return sb.toString();
    }

    /**
     * Creates nice textual representation of KeyStroke.
     * Modifiers and an actual key label are concated by plus signs
     * @param the KeyStroke to get description of
     * @return String describing the KeyStroke
     */
    public static String keyStrokeToString( KeyStroke stroke ) {
        String modifText = KeyEvent.getKeyModifiersText( stroke.getModifiers() );
        String keyText = (stroke.getKeyCode() == KeyEvent.VK_UNDEFINED) ? 
            String.valueOf(stroke.getKeyChar()) : getKeyText(stroke.getKeyCode());
        if( modifText.length() > 0 ) return modifText + '+' + keyText;
        else return keyText;
    }
    
    /** @return slight modification of what KeyEvent.getKeyText() returns.
     *  The numpad Left, Right, Down, Up get extra result.
     */
    private static String getKeyText(int keyCode) {
        String ret = KeyEvent.getKeyText(keyCode);
        if (ret != null) {
            switch (keyCode) {
                case KeyEvent.VK_KP_DOWN:
                    ret = prefixNumpad(ret, KeyEvent.VK_DOWN);
                    break;
                case KeyEvent.VK_KP_LEFT:
                    ret = prefixNumpad(ret, KeyEvent.VK_LEFT);
                    break;
                case KeyEvent.VK_KP_RIGHT:
                    ret = prefixNumpad(ret, KeyEvent.VK_RIGHT);
                    break;
                case KeyEvent.VK_KP_UP:
                    ret = prefixNumpad(ret, KeyEvent.VK_UP);
                    break;
            }
        }
        return ret;
    }
    
    private static String prefixNumpad(String key, int testKeyCode) {
        if (key.equals(KeyEvent.getKeyText(testKeyCode))) {
            key = LocaleSupport.getString("key-prefix-numpad") + key;
        }
        return key;
    }

    private static void checkOffsetValid(Document doc, int offset) throws BadLocationException {
        checkOffsetValid(offset, doc.getLength());
    }

    private static void checkOffsetValid(int offset, int limitOffset) throws BadLocationException {
        if (offset < 0 || offset > limitOffset) { 
            throw new BadLocationException("Invalid offset=" + offset // NOI18N
                + " not within <0, " + limitOffset + ">", // NOI18N
                offset);
        }
    }

    /** Annotates a Throwable if ErrorManager.INFORMATIONAL is loggable */
    public static void annotateLoggable(Throwable t){
        if (isLoggable){
            ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, t);
        }
    }
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy