/* * TextArea.java - Abstract jEdit Text Area component * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 1999, 2005 Slava Pestov * Portions copyright (C) 2000 Ollie Rutherfurd * Portions copyright (C) 2006 Matthieu Casanova * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.textarea; //{{{ Imports import java.util.EventObject; import java.util.Iterator; import java.util.LinkedList; import java.util.TooManyListenersException; import java.text.BreakIterator; import java.text.CharacterIterator; import javax.annotation.Nonnull; import javax.swing.*; import javax.swing.event.*; import java.awt.event.*; import java.awt.*; import java.awt.im.InputMethodRequests; import javax.swing.plaf.metal.MetalLookAndFeel; import javax.swing.plaf.LayerUI; import javax.swing.text.Segment; import javax.swing.text.TabExpander; import org.gjt.sp.jedit.Debug; import org.gjt.sp.jedit.IPropertyManager; import org.gjt.sp.jedit.JEditActionContext; import org.gjt.sp.jedit.JEditActionSet; import org.gjt.sp.jedit.JEditBeanShellAction; import org.gjt.sp.jedit.TextUtilities; import org.gjt.sp.jedit.buffer.JEditBuffer; import org.gjt.sp.jedit.input.AbstractInputHandler; import org.gjt.sp.jedit.input.DefaultInputHandlerProvider; import org.gjt.sp.jedit.input.InputHandlerProvider; import org.gjt.sp.jedit.input.TextAreaInputHandler; import org.gjt.sp.jedit.syntax.Chunk; import org.gjt.sp.jedit.syntax.DefaultTokenHandler; import org.gjt.sp.jedit.syntax.Token; import org.gjt.sp.util.Log; import org.gjt.sp.util.StandardUtilities; import org.gjt.sp.util.ThreadUtilities; //}}} /** Abstract TextArea component. * * The concrete instance used by jEdit itself is called the JEditTextArea. * * This class uses a minimal set of jEdit APIs because it is the base class of the * JEditEmbeddedTextArea and StandaloneTextArea, so it needs to be embeddable and separable. * * @author Slava Pestov * @author kpouer (rafactoring into standalone text area) * @version $Id: TextArea.java 23651 2014-08-13 16:51:39Z vampire0 $ */ public abstract class TextArea extends JPanel { //{{{ TextArea constructor /** * Creates a new JEditTextArea. * @param propertyManager the property manager that contains informations like shortcut bindings * @param inputHandlerProvider the inputHandlerProvider */ protected TextArea(IPropertyManager propertyManager, InputHandlerProvider inputHandlerProvider) { this.inputHandlerProvider = inputHandlerProvider; enableEvents(AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK); //{{{ Initialize some misc. stuff selectionManager = new SelectionManager(this); chunkCache = new ChunkCache(this); painter = new TextAreaPainter(this); gutter = new Gutter(this); gutter.setMouseActionsProvider(new MouseActions(propertyManager, "gutter")); listenerList = new EventListenerList(); caretEvent = new MutableCaretEvent(); blink = true; offsetXY = new Point(); structureMatchers = new LinkedList<StructureMatcher>(); structureMatchers.add(new StructureMatcher.BracketMatcher()); //}}} //{{{ Initialize the GUI setLayout(new ScrollLayout()); add(ScrollLayout.CENTER,painter); add(ScrollLayout.LEFT,gutter); // some plugins add stuff in a "right-hand" gutter RequestFocusLayerUI reqFocus = new RequestFocusLayerUI(); verticalBox = new Box(BoxLayout.X_AXIS); verticalBox.add(new JLayer( vertical = new JScrollBar(Adjustable.VERTICAL), reqFocus)); vertical.setRequestFocusEnabled(false); add(ScrollLayout.RIGHT,verticalBox); add(ScrollLayout.BOTTOM, new JLayer( horizontal = new JScrollBar(Adjustable.HORIZONTAL), reqFocus)); horizontal.setRequestFocusEnabled(false); horizontal.setValues(0,0,0,0); //}}} //{{{ this ensures that the text area's look is slightly // more consistent with the rest of the metal l&f. // while it depends on not-so-well-documented portions // of Swing, it only affects appearance, so future // breakage shouldn't matter if(UIManager.getLookAndFeel() instanceof MetalLookAndFeel) { setBorder(new TextAreaBorder()); vertical.putClientProperty("JScrollBar.isFreeStanding", Boolean.FALSE); horizontal.putClientProperty("JScrollBar.isFreeStanding", Boolean.FALSE); //horizontal.setBorder(null); } //}}} //{{{ Add some event listeners vertical.addAdjustmentListener(new AdjustHandler()); horizontal.addAdjustmentListener(new AdjustHandler()); addFocusListener(new FocusHandler()); addMouseWheelListener(new MouseWheelHandler()); //}}} // This doesn't seem very correct, but it fixes a problem // when setting the initial caret position for a buffer // (eg, from the recent file list) focusedComponent = this; } //}}} //{{{ getFoldPainter() method public FoldPainter getFoldPainter() { return new TriangleFoldPainter(); } //}}} //{{{ initInputHandler() method /** * Creates an actionContext and initializes the input * handler for this textarea. Called when creating * a standalone textarea from within jEdit. */ public void initInputHandler() { actionContext = new JEditActionContext<JEditBeanShellAction, JEditActionSet<JEditBeanShellAction>>() { @Override public void invokeAction(EventObject evt, JEditBeanShellAction action) { action.invoke(TextArea.this); } }; setMouseHandler(new TextAreaMouseHandler(this)); inputHandlerProvider = new DefaultInputHandlerProvider(new TextAreaInputHandler(this) { @Override protected JEditBeanShellAction getAction(String action) { return actionContext.getAction(action); } }); } //}}} //{{{ getActionContext() method public JEditActionContext<JEditBeanShellAction,JEditActionSet<JEditBeanShellAction>> getActionContext() { return actionContext; } //}}} //{{{ setMouseHandler() method public void setMouseHandler(MouseInputAdapter mouseInputAdapter) { mouseHandler = mouseInputAdapter; painter.addMouseListener(mouseHandler); painter.addMouseMotionListener(mouseHandler); } //}}} //{{{ setTransferHandler() method @Override public void setTransferHandler(TransferHandler newHandler) { super.setTransferHandler(newHandler); try { getDropTarget().addDropTargetListener( new TextAreaDropHandler(this)); } catch(TooManyListenersException e) { Log.log(Log.ERROR,this,e); } } //}}} //{{{ toString() method @Override public String toString() { StringBuilder builder = new StringBuilder(); String baseVersion = super.toString(); int len = baseVersion.length() - 1; builder.append(baseVersion); builder.setLength(len); // chop off the last ] builder.append(",caret=").append(caret); builder.append(",caretLine=").append(caretLine); builder.append(",caretScreenLine=").append(caretScreenLine); builder.append(",electricScroll=").append(electricScroll); builder.append(",horizontalOffset=").append(horizontalOffset); builder.append(",magicCaret=").append(magicCaret); builder.append(",offsetXY=").append(offsetXY.toString()); builder.append(",oldCaretLine=").append(oldCaretLine); builder.append(",screenLastLine=").append(screenLastLine); builder.append(",visibleLines=").append(visibleLines); builder.append(",firstPhysicalLine=").append(getFirstPhysicalLine()); builder.append(",physLastLine=").append(physLastLine).append("]"); return builder.toString(); } //}}} //{{{ dispose() method /** * Plugins and macros should not call this method. * @since jEdit 4.2pre1 */ public void dispose() { DisplayManager.textAreaDisposed(this); gutter.dispose(); } //}}} //{{{ getInputHandler() method /** * @since jEdit 4.3pre1 */ public AbstractInputHandler getInputHandler() { return inputHandlerProvider.getInputHandler(); } //}}} //{{{ getPainter() method /** * Returns the object responsible for painting this text area. */ public final TextAreaPainter getPainter() { return painter; } //}}} //{{{ getGutter() method /** * Returns the gutter to the left of the text area or null if the gutter * is disabled */ public final Gutter getGutter() { return gutter; } //}}} //{{{ getDisplayManager() method /** * @return the display manager used by this text area. * @since jEdit 4.2pre1 */ public DisplayManager getDisplayManager() { return displayManager; } //}}} //{{{ isCaretBlinkEnabled() method /** * @return true if the caret is blinking, false otherwise. */ public final boolean isCaretBlinkEnabled() { return caretBlinks; } //}}} //{{{ setCaretBlinkEnabled() method /** * Toggles caret blinking. * @param caretBlinks True if the caret should blink, false otherwise */ public void setCaretBlinkEnabled(boolean caretBlinks) { this.caretBlinks = caretBlinks; if(!caretBlinks) blink = false; if(buffer != null) invalidateLine(caretLine); } //}}} //{{{ getElectricScroll() method /** * @return the minimum distance (in number of lines) * from the caret to the nearest edge of the screen * (top or bottom edge). */ public final int getElectricScroll() { return electricScroll; } //}}} //{{{ setElectricScroll() method /** * Sets the number of lines from the top and bottom of the text * area that are always visible * @param electricScroll The number of lines always visible from * the top or bottom */ public final void setElectricScroll(int electricScroll) { this.electricScroll = electricScroll; } //}}} //{{{ isQuickCopyEnabled() method /** * Returns if clicking the middle mouse button pastes the most * recent selection (% register), and if Control-dragging inserts * the selection at the caret. */ public final boolean isQuickCopyEnabled() { return quickCopy; } //}}} //{{{ setQuickCopyEnabled() method /** * Sets if clicking the middle mouse button pastes the most * recent selection (% register), and if Control-dragging inserts * the selection at the caret. * @param quickCopy A boolean flag */ public final void setQuickCopyEnabled(boolean quickCopy) { this.quickCopy = quickCopy; } //}}} //{{{ getBuffer() method /** * Returns the buffer this text area is editing. * @since jedit 4.3pre3 * * Prior to 4.3pre3, this function returned a "Buffer" type. * If this causes your code to break, try calling view.getBuffer() instead of * view.getTextArea().getBuffer(). * */ public final JEditBuffer getBuffer() { return buffer; } //}}} //{{{ setBuffer() method /** * Sets the buffer this text area is editing. * If you don't run a standalone textarea in jEdit please do not call this method - * use {@link org.gjt.sp.jedit.EditPane#setBuffer(org.gjt.sp.jedit.Buffer)} instead. * @param buffer The buffer */ public void setBuffer(JEditBuffer buffer) { if(this.buffer == buffer) return; try { bufferChanging = true; boolean inCompoundEdit = false; if(this.buffer != null) { // dubious? //setFirstLine(0); if(!this.buffer.isLoading()) selectNone(); caretLine = caret = caretScreenLine = 0; match = null; // is the current buffer performing a compoundEdit? inCompoundEdit = this.buffer.insideCompoundEdit(); if (inCompoundEdit) this.buffer.endCompoundEdit(); } // set new buffer this.buffer = buffer; // old buffer did perform a compoundEdit, // so open a compoundEdit for new buffer if (inCompoundEdit) this.buffer.beginCompoundEdit(); chunkCache.setBuffer(buffer); gutter.setBuffer(buffer); propertiesChanged(); if(displayManager != null) { displayManager.release(); } displayManager = DisplayManager.getDisplayManager( buffer,this); displayManager.init(); if(buffer.isLoading()) updateScrollBar(); repaint(); fireScrollEvent(true); } finally { bufferChanging = false; } } //}}} //{{{ isEditable() method /** * Returns true if this text area is editable, false otherwise. */ public final boolean isEditable() { return buffer.isEditable(); } //}}} //{{{ isDragEnabled() method /** * Returns if drag and drop of text is enabled. * @since jEdit 4.2pre5 */ public boolean isDragEnabled() { return dndEnabled; } //}}} //{{{ setDragEnabled() method /** * Sets if drag and drop of text is enabled. * @since jEdit 4.2pre5 */ public void setDragEnabled(boolean dndEnabled) { this.dndEnabled = dndEnabled; } //}}} //{{{ getJoinNonWordChars() method /** * If set, double clicking will join non-word characters to form one "word". * @since jEdit 4.3pre2 */ public boolean getJoinNonWordChars() { return joinNonWordChars; } //}}} //{{{ setJoinNonWordChars() method /** * If set, double clicking will join non-word characters to form one "word". * @since jEdit 4.3pre2 */ public void setJoinNonWordChars(boolean joinNonWordChars) { this.joinNonWordChars = joinNonWordChars; } //}}} //{{{ getCtrlForRectangularSelection() method /** * If set, CTRL enables rectangular selection mode while pressed. * @since jEdit 4.3pre10 */ public boolean isCtrlForRectangularSelection() { return ctrlForRectangularSelection; } //}}} //{{{ setCtrlForRectangularSelection() method /** * If set, CTRL enables rectangular selection mode while pressed. * @since jEdit 4.3pre10 */ public void setCtrlForRectangularSelection(boolean ctrlForRectangularSelection) { this.ctrlForRectangularSelection = ctrlForRectangularSelection; } //}}} //{{{ Scrolling //{{{ getFirstLine() method /** * Returns the vertical scroll bar position. * @since jEdit 4.2pre1 */ public final int getFirstLine() { return displayManager.firstLine.getScrollLine() + displayManager.firstLine.getSkew(); } //}}} //{{{ setFirstLine() method /** * Sets the vertical scroll bar position * * @param firstLine The scroll bar position */ public void setFirstLine(int firstLine) { //{{{ ensure we don't have empty space at the bottom or top, etc int max = displayManager.getScrollLineCount() - visibleLines + (lastLinePartial ? 1 : 0); if(firstLine > max) firstLine = max; if(firstLine < 0) firstLine = 0; //}}} int oldFirstLine = getFirstLine(); if(Debug.SCROLL_DEBUG) { Log.log(Log.DEBUG,this,"setFirstLine() from " + oldFirstLine + " to " + firstLine); } if(firstLine == oldFirstLine) return; displayManager.setFirstLine(oldFirstLine,firstLine); repaint(); fireScrollEvent(true); } //}}} //{{{ getFirstPhysicalLine() method /** * Returns the first visible physical line index. * @since jEdit 4.0pre4 */ public final int getFirstPhysicalLine() { return displayManager.firstLine.getPhysicalLine(); } //}}} //{{{ setFirstPhysicalLine() methods /** * Sets the vertical scroll bar position. * @param physFirstLine The first physical line to display * @since jEdit 4.2pre1 */ public void setFirstPhysicalLine(int physFirstLine) { setFirstPhysicalLine(physFirstLine,0); } /** * Sets the vertical scroll bar position. * @param physFirstLine The first physical line to display * @param skew A local screen line delta * @since jEdit 4.2pre1 */ public void setFirstPhysicalLine(int physFirstLine, int skew) { if(Debug.SCROLL_DEBUG) { Log.log(Log.DEBUG,this,"setFirstPhysicalLine(" + physFirstLine + ',' + skew + ')'); } int amount = physFirstLine - displayManager.firstLine.getPhysicalLine(); displayManager.setFirstPhysicalLine(amount,skew); repaint(); fireScrollEvent(true); } //}}} //{{{ getLastPhysicalLine() method /** * Returns the last visible physical line index. * @since jEdit 4.0pre4 */ public final int getLastPhysicalLine() { return physLastLine; } //}}} //{{{ getLastScreenLine() method /** * Returns the last screen line index, it is different from * {@link #getVisibleLines()} because the buffer can have less lines than * the visible lines * @return the last screen line index. * @since jEdit 4.3pre1 */ public int getLastScreenLine() { return screenLastLine; } //}}} //{{{ getVisibleLines() method /** * Returns the number of lines visible in this text area. * @return the number of visible lines in the textarea */ public final int getVisibleLines() { return visibleLines; } //}}} //{{{ getHorizontalOffset() method /** * Returns the horizontal offset of drawn lines. */ public final int getHorizontalOffset() { return horizontalOffset; } //}}} //{{{ setHorizontalOffset() method /** * Sets the horizontal offset of drawn lines. This can be used to * implement horizontal scrolling. * @param horizontalOffset offset The new horizontal offset */ public void setHorizontalOffset(int horizontalOffset) { if(horizontalOffset > 0) horizontalOffset = 0; if(horizontalOffset == this.horizontalOffset) return; this.horizontalOffset = horizontalOffset; painter.repaint(); fireScrollEvent(false); } //}}} //{{{ scrollUpLine() method /** * Scrolls up by one line. * @since jEdit 2.7pre2 */ public void scrollUpLine() { setFirstLine(getFirstLine() - 1); } //}}} //{{{ scrollUpPage() method /** * Scrolls up by one page. * @since jEdit 2.7pre2 */ public void scrollUpPage() { setFirstLine(getFirstLine() - getVisibleLines() + (lastLinePartial ? 1 : 0)); } //}}} //{{{ scrollDownLine() method /** * Scrolls down by one line. * @since jEdit 2.7pre2 */ public void scrollDownLine() { setFirstLine(getFirstLine() + 1); } //}}} //{{{ scrollDownPage() method /** * Scrolls down by one page. * @since jEdit 2.7pre2 */ public void scrollDownPage() { setFirstLine(getFirstLine() + getVisibleLines() - (lastLinePartial ? 1 : 0)); } //}}} //{{{ scrollToCaret() method /** * Ensures that the caret is visible by scrolling the text area if * necessary. * @param doElectricScroll If true, electric scrolling will be performed */ public void scrollToCaret(boolean doElectricScroll) { scrollTo(caretLine,caret - buffer.getLineStartOffset(caretLine), doElectricScroll); } //}}} //{{{ scrollTo() methods /** * Ensures that the specified location in the buffer is visible. * @param offset The offset from the start of the buffer * @param doElectricScroll If true, electric scrolling will be performed * @since jEdit 4.2pre3 */ public void scrollTo(int offset, boolean doElectricScroll) { int line = buffer.getLineOfOffset(offset); scrollTo(line,offset - buffer.getLineStartOffset(line), doElectricScroll); } /** * Ensures that the specified location in the buffer is visible. * @param line The line number * @param offset The offset from the start of the line * @param doElectricScroll If true, electric scrolling will be performed * @since jEdit 4.0pre6 */ public void scrollTo(int line, int offset, boolean doElectricScroll) { if (buffer.isLoading()) return; if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,"scrollTo(), lineCount=" + getLineCount()); if(visibleLines <= 1) { if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,"visibleLines <= 0"); // Fix the case when the line is wrapped // it was not possible to see the second (or next) // subregion of a line ChunkCache.LineInfo[] infos = chunkCache .getLineInfosForPhysicalLine(line); int subregion = ChunkCache.getSubregionOfOffset( offset,infos); setFirstPhysicalLine(line,subregion); return; } //{{{ Get ready int extraEndVirt; int lineLength = buffer.getLineLength(line); if(offset > lineLength) { extraEndVirt = charWidth * (offset - lineLength); offset = lineLength; } else extraEndVirt = 0; int _electricScroll = doElectricScroll && visibleLines - 1 > (electricScroll << 1) ? electricScroll : 0; //}}} //{{{ Scroll vertically int screenLine = chunkCache.getScreenLineOfOffset(line,offset); int visibleLines = getVisibleLines(); if(screenLine == -1) { // We are scrolling to a position that is not on the screen. if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,"screenLine == -1"); ChunkCache.LineInfo[] infos = chunkCache .getLineInfosForPhysicalLine(line); int subregion = ChunkCache.getSubregionOfOffset( offset,infos); int prevLine = displayManager.getPrevVisibleLine(getFirstPhysicalLine()); int nextLine = displayManager.getNextVisibleLine(getLastPhysicalLine()); if(line == getFirstPhysicalLine()) { if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,line + " == " + getFirstPhysicalLine()); setFirstPhysicalLine(line,subregion - _electricScroll); } else if(line == prevLine) { if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,line + " == " + prevLine); setFirstPhysicalLine(prevLine,subregion - _electricScroll); } else if(line == getLastPhysicalLine()) { if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,line + " == " + getLastPhysicalLine()); setFirstPhysicalLine(line, subregion + _electricScroll - visibleLines + (lastLinePartial ? 2 : 1)); } else if(line == nextLine) { if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,line + " == " + nextLine); setFirstPhysicalLine(nextLine, subregion + _electricScroll - visibleLines + (lastLinePartial ? 2 : 1)); } else { if(Debug.SCROLL_TO_DEBUG) { Log.log(Log.DEBUG,this,"neither"); Log.log(Log.DEBUG,this,"Last physical line is " + getLastPhysicalLine()); } setFirstPhysicalLine(line,subregion - (visibleLines >> 1)); if(Debug.SCROLL_TO_DEBUG) { Log.log(Log.DEBUG,this,"Last physical line is " + getLastPhysicalLine()); } } } else if(screenLine < _electricScroll) { if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,"electric up"); setFirstLine(getFirstLine() - _electricScroll + screenLine); } else if(screenLine > visibleLines - _electricScroll - (lastLinePartial ? 2 : 1)) { if(Debug.SCROLL_TO_DEBUG) Log.log(Log.DEBUG,this,"electric down"); setFirstLine(getFirstLine() + _electricScroll - visibleLines + screenLine + (lastLinePartial ? 2 : 1)); } //}}} //{{{ Scroll horizontally if(!displayManager.isLineVisible(line)) return; Point point = offsetToXY(line,offset,offsetXY); point.x += extraEndVirt; if(point.x < 0) { setHorizontalOffset(horizontalOffset - point.x + charWidth + 5); } else if(point.x >= painter.getWidth() - charWidth - 5) { setHorizontalOffset(horizontalOffset + (painter.getWidth() - point.x) - charWidth - 5); } //}}} } //}}} //{{{ addScrollListener() method /** * Adds a scroll listener to this text area. * @param listener The listener * @since jEdit 3.2pre2 */ public final void addScrollListener(ScrollListener listener) { listenerList.add(ScrollListener.class,listener); } //}}} //{{{ removeScrollListener() method /** * Removes a scroll listener from this text area. * @param listener The listener * @since jEdit 3.2pre2 */ public final void removeScrollListener(ScrollListener listener) { listenerList.remove(ScrollListener.class,listener); } //}}} //}}} //{{{ Screen line stuff //{{{ getPhysicalLineOfScreenLine() method /** * Returns the physical line number that contains the specified screen * line. * @param screenLine The screen line * @since jEdit 4.0pre6 */ public int getPhysicalLineOfScreenLine(int screenLine) { return chunkCache.getLineInfo(screenLine).physicalLine; } //}}} //{{{ getScreenLineOfOffset() method /** * Returns the screen (wrapped) line containing the specified offset. * Returns -1 if the line is not currently visible on the screen. * @param offset The offset * @since jEdit 4.0pre4 */ public int getScreenLineOfOffset(int offset) { int line = buffer.getLineOfOffset(offset); offset -= buffer.getLineStartOffset(line); return chunkCache.getScreenLineOfOffset(line,offset); } //}}} //{{{ getScreenLineStartOffset() method /** * Returns the start offset of the specified screen (wrapped) line. * @param line The line * @since jEdit 4.0pre4 */ public int getScreenLineStartOffset(int line) { ChunkCache.LineInfo lineInfo = chunkCache.getLineInfo(line); if(lineInfo.physicalLine == -1) return -1; return buffer.getLineStartOffset(lineInfo.physicalLine) + lineInfo.offset; } //}}} //{{{ getScreenLineEndOffset() method /** * Returns the end offset of the specified screen (wrapped) line. * @param line The line * @since jEdit 4.0pre4 */ public int getScreenLineEndOffset(int line) { ChunkCache.LineInfo lineInfo = chunkCache.getLineInfo(line); if(lineInfo.physicalLine == -1) return -1; return buffer.getLineStartOffset(lineInfo.physicalLine) + lineInfo.offset + lineInfo.length; } //}}} //}}} //{{{ Offset conversion //{{{ xyToOffset() methods /** * Converts a point to an offset. * Note that unlike in previous jEdit versions, this method now returns * -1 if the y co-ordinate is out of bounds. * * @param x The x co-ordinate of the point * @param y The y co-ordinate of the point */ public int xyToOffset(int x, int y) { return xyToOffset(x,y,true); } /** * Converts a point to an offset. * Note that unlike in previous jEdit versions, this method now returns * -1 if the y co-ordinate is out of bounds. * * @param x The x co-ordinate of the point * @param y The y co-ordinate of the point * @param round Round up to next character if past the middle of a character? * @since jEdit 3.2pre6 */ public int xyToOffset(int x, int y, boolean round) { int height = painter.getLineHeight(); int line = y / height; if(line < 0 || line >= visibleLines) return -1; return xToScreenLineOffset(line,x,round); } //}}} //{{{ xToScreenLineOffset() method /** * Converts a point in a given screen line to an offset. * * @param x The x co-ordinate of the point * @param screenLine The screen line * @param round Round up to next character if past the middle of a character? * @since jEdit 3.2pre6 */ public int xToScreenLineOffset(int screenLine, int x, boolean round) { ChunkCache.LineInfo lineInfo = chunkCache.getLineInfo(screenLine); if(lineInfo.physicalLine == -1) { return getLineEndOffset(displayManager .getLastVisibleLine()) - 1; } else { float xInLine = x - horizontalOffset; int offsetInLine = Chunk.xToOffset(lineInfo.chunks, xInLine, false); int lineStartOffset = getLineStartOffset(lineInfo.physicalLine); if (offsetInLine == -1 || offsetInLine == lineInfo.offset + lineInfo.length) return lineStartOffset + lineInfo.offset + lineInfo.length - 1; int offset = lineStartOffset + offsetInLine; final LineCharacterBreaker charBreaker = new LineCharacterBreaker(this, offset); int lower = charBreaker.offsetIsBoundary(offset) ? offset : charBreaker.previousOf(offset); if (round) { float lowerX = Chunk.offsetToX(lineInfo.chunks, lower - lineStartOffset); int upper = charBreaker.nextOf(lower); float upperX = Chunk.offsetToX(lineInfo.chunks, upper - lineStartOffset); return (xInLine < ((lowerX + upperX) / 2)) ? lower : upper; } else { return lower; } } } //}}} //{{{ offsetToXY() methods /** * Converts an offset into a point in the text area painter's * co-ordinate space. * @param offset The offset * @return The location of the offset on screen, or <code>null</code> * if the specified offset is not visible */ public Point offsetToXY(int offset) { int line = buffer.getLineOfOffset(offset); offset -= buffer.getLineStartOffset(line); Point retVal = new Point(); return offsetToXY(line,offset,retVal); } /** * Converts an offset into a point in the text area painter's * co-ordinate space. * @param line The line * @param offset The offset * @return The location of the offset on screen, or <code>null</code> * if the specified offset is not visible */ public Point offsetToXY(int line, int offset) { return offsetToXY(line,offset,new Point()); } /** * Converts a line,offset pair into an x,y (pixel) point relative to the * upper left corner (0,0) of the text area. * * @param line The physical line number (from top of document) * @param offset The offset in characters, from the start of the line * @param retVal The point to store the return value in * @return <code>retVal</code> for convenience, or <code>null</code> * if the specified offset is not visible * @since jEdit 4.0pre4 */ public Point offsetToXY(int line, int offset, Point retVal) { if(!displayManager.isLineVisible(line)) return null; int screenLine = chunkCache.getScreenLineOfOffset(line,offset); if(screenLine == -1) return null; retVal.y = screenLine * painter.getLineHeight(); ChunkCache.LineInfo info = chunkCache.getLineInfo(screenLine); retVal.x = (int)(horizontalOffset + Chunk.offsetToX( info.chunks,offset)); return retVal; } //}}} //}}} //{{{ Painting //{{{ invalidateScreenLineRange() method /** * Marks a range of screen lines as needing a repaint. * @param start The first line * @param end The last line * @since jEdit 4.0pre4 */ public void invalidateScreenLineRange(int start, int end) { if(buffer.isLoading()) return; if(start > end) { int tmp = end; end = start; start = tmp; } if(chunkCache.needFullRepaint()) end = visibleLines; int y = start * painter.getLineHeight(); int height = (end - start + 1) * painter.getLineHeight(); painter.repaint(0,y,painter.getWidth(),height); gutter.repaint(0,y,gutter.getWidth(),height); } //}}} //{{{ invalidateLine() method /** * Marks a line as needing a repaint. * @param line The physical line to invalidate */ public void invalidateLine(int line) { if(!isShowing() || buffer.isLoading() || line < getFirstPhysicalLine() || line > physLastLine || !displayManager.isLineVisible(line)) return; int startLine = -1; int endLine = -1; for(int i = 0; i < visibleLines; i++) { ChunkCache.LineInfo info = chunkCache.getLineInfo(i); if((info.physicalLine >= line || info.physicalLine == -1) && startLine == -1) { startLine = i; } if((info.physicalLine >= line && info.lastSubregion) || info.physicalLine == -1) { endLine = i; break; } } if(chunkCache.needFullRepaint() || endLine == -1) endLine = visibleLines; invalidateScreenLineRange(startLine,endLine); } //}}} //{{{ invalidateLineRange() method /** * Marks a range of physical lines as needing a repaint. * @param start The first line to invalidate * @param end The last line to invalidate */ public void invalidateLineRange(int start, int end) { if(!isShowing() || buffer.isLoading()) return; if(end < start) { int tmp = end; end = start; start = tmp; } if(end < getFirstPhysicalLine() || start > getLastPhysicalLine()) return; int startScreenLine = -1; int endScreenLine = -1; for(int i = 0; i < visibleLines; i++) { ChunkCache.LineInfo info = chunkCache.getLineInfo(i); if((info.physicalLine >= start || info.physicalLine == -1) && startScreenLine == -1) { startScreenLine = i; } if((info.physicalLine >= end && info.lastSubregion) || info.physicalLine == -1) { endScreenLine = i; break; } } if(startScreenLine == -1) startScreenLine = 0; if(chunkCache.needFullRepaint() || endScreenLine == -1) endScreenLine = visibleLines; invalidateScreenLineRange(startScreenLine,endScreenLine); } //}}} //}}} //{{{ Convenience methods //{{{ getBufferLength() method /** * Returns the length of the buffer. */ public final int getBufferLength() { return buffer.getLength(); } //}}} //{{{ getLineCount() method /** * Returns the number of physical lines in the buffer. */ public final int getLineCount() { return buffer.getLineCount(); } //}}} //{{{ getLineOfOffset() method /** * Returns the line containing the specified offset. * @param offset The offset */ public final int getLineOfOffset(int offset) { return buffer.getLineOfOffset(offset); } //}}} //{{{ getLineStartOffset() method /** * Returns the start offset of the specified line. * @param line The line (physical line) * @return The start offset of the specified line, or -1 if the line is * invalid */ public int getLineStartOffset(int line) { return buffer.getLineStartOffset(line); } //}}} //{{{ getLineEndOffset() method /** * Returns the end offset of the specified line. * @param line The line (physical line) * @return The end offset of the specified line, or -1 if the line is * invalid. */ public int getLineEndOffset(int line) { return buffer.getLineEndOffset(line); } //}}} //{{{ getLineLength() method /** * Returns the length of the specified line. * @param line The line */ public int getLineLength(int line) { return buffer.getLineLength(line); } //}}} //{{{ getText() methods /** * Returns the specified substring of the buffer. * @param start The start offset * @param len The length of the substring * @return The substring */ public final String getText(int start, int len) { return buffer.getText(start,len); } /** * Copies the specified substring of the buffer into a segment. * @param start The start offset * @param len The length of the substring * @param segment The segment */ public final void getText(int start, int len, Segment segment) { buffer.getText(start,len,segment); } /** * Returns the entire text of this text area. */ public String getText() { return buffer.getText(0,buffer.getLength()); } //}}} //{{{ getLineText() methods /** * Returns the text on the specified line. * @param lineIndex the line number * @return The text, or null if the lineIndex is invalid */ public final String getLineText(int lineIndex) { return buffer.getLineText(lineIndex); } /** * Copies the text on the specified line into a Segment. If lineIndex * is invalid, the segment will contain a null string. * @param lineIndex The line number (physical line) * @param segment the segment into which the data will be stored. */ public final void getLineText(int lineIndex, Segment segment) { buffer.getLineText(lineIndex,segment); } //}}} //{{{ getVisibleLineText() methods /** * Returns the visible part of the given line * @param screenLine the screenLine * @return the visible text * @since 4.5pre1 */ public String getVisibleLineText(int screenLine) { int offset = -getHorizontalOffset(); ChunkCache.LineInfo lineInfo = chunkCache.getLineInfo(screenLine); int lineStartOffset = getLineStartOffset(lineInfo.physicalLine); Point point = offsetToXY(lineStartOffset + lineInfo.offset); int begin = xyToOffset(offset + point.x, point.y); int end = xyToOffset(getPainter().getWidth(), point.y); return buffer.getText(begin, end - begin); } /** * Returns the visible part of the given line * @param screenLine the screenLine * @param segment the segment into which the data will be stored. * @since 4.5pre1 */ public void getVisibleLineText(int screenLine, Segment segment) { int offset = -getHorizontalOffset(); ChunkCache.LineInfo lineInfo = chunkCache.getLineInfo(screenLine); int lineStartOffset = getLineStartOffset(lineInfo.physicalLine); Point point = offsetToXY(lineStartOffset + lineInfo.offset); int begin = xyToOffset(offset + point.x, point.y); int end = xyToOffset(getPainter().getWidth(), point.y); buffer.getText(begin, end - begin, segment); }//}}} //{{{ getVisibleLineSegment() method /** * Returns the visible part of the given line in a CharSequence. * The buffer data are not copied. so this should be used in EDT * thread * @param screenLine the screenLine * @return the visible text * @since 4.5pre1 */ public CharSequence getVisibleLineSegment(int screenLine) { int offset = -getHorizontalOffset(); ChunkCache.LineInfo lineInfo = chunkCache.getLineInfo(screenLine); int lineStartOffset = getLineStartOffset(lineInfo.physicalLine); Point point = offsetToXY(lineStartOffset + lineInfo.offset); int begin = xyToOffset(offset + point.x, point.y); int end = xyToOffset(getPainter().getWidth(), point.y); return buffer.getSegment(begin, end - begin); } //}}} //{{{ setText() method /** * Sets the entire text of this text area. * @param text the new content of the buffer */ public void setText(String text) { try { buffer.beginCompoundEdit(); buffer.remove(0,buffer.getLength()); buffer.insert(0,text); } finally { buffer.endCompoundEdit(); } } //}}} //{{{ Selection //{{{ selectAll() method /** * Selects all text in the buffer. Preserves the scroll position. */ public final void selectAll() { int firstLine = getFirstLine(); int horizOffset = getHorizontalOffset(); setSelection(new Selection.Range(0,buffer.getLength())); moveCaretPosition(buffer.getLength(),true); setFirstLine(firstLine); setHorizontalOffset(horizOffset); } //}}} //{{{ selectLine() method /** * Selects the current line. * @since jEdit 2.7pre2 */ public void selectLine() { int caretLine = getCaretLine(); int start = getLineStartOffset(caretLine); int end = getLineEndOffset(caretLine) - 1; Selection s = new Selection.Range(start,end); if(multi) addToSelection(s); else setSelection(s); moveCaretPosition(end); } //}}} //{{{ selectParagraph() method /** * Selects the paragraph at the caret position. * @since jEdit 2.7pre2 */ public void selectParagraph() { int caretLine = getCaretLine(); if(getLineLength(caretLine) == 0) { getToolkit().beep(); return; } int start = caretLine; int end = caretLine; while(start >= 0) { if(getLineLength(start) == 0) break; else start--; } while(end < getLineCount()) { if(getLineLength(end) == 0) break; else end++; } int selectionStart = getLineStartOffset(start + 1); int selectionEnd = getLineEndOffset(end - 1) - 1; Selection s = new Selection.Range(selectionStart,selectionEnd); if(multi) addToSelection(s); else setSelection(s); moveCaretPosition(selectionEnd); } //}}} //{{{ selectWord() method /** * Selects the word at the caret position. * @since jEdit 2.7pre2 */ public void selectWord() { int line = getCaretLine(); int lineStart = getLineStartOffset(line); int offset = getCaretPosition() - lineStart; if(getLineLength(line) == 0) return; String lineText = getLineText(line); String noWordSep = buffer.getStringProperty("noWordSep"); if(offset == getLineLength(line)) offset--; int wordStart = TextUtilities.findWordStart(lineText,offset, noWordSep,true,false,false); int wordEnd = TextUtilities.findWordEnd(lineText,offset+1, noWordSep,true,false,false); Selection s = new Selection.Range(lineStart + wordStart, lineStart + wordEnd); if(multi) addToSelection(s); else setSelection(s); moveCaretPosition(lineStart + wordEnd); } //}}} //{{{ selectToMatchingBracket() method /** * Selects from the bracket at the specified position to the * corresponding bracket. * @since jEdit 4.2pre1 */ public Selection selectToMatchingBracket(int position, boolean quickCopy) { int positionLine = buffer.getLineOfOffset(position); int lineOffset = position - buffer.getLineStartOffset(positionLine); if(getLineLength(positionLine) != 0) { int bracket = TextUtilities.findMatchingBracket(buffer, positionLine,Math.max(0,lineOffset - 1)); if(bracket != -1) { Selection s; if(bracket < position) { if(!quickCopy) moveCaretPosition(position,false); s = new Selection.Range(bracket,position); } else { if(!quickCopy) moveCaretPosition(bracket + 1,false); s = new Selection.Range(position - 1,bracket + 1); } if(!multi && !quickCopy) selectNone(); addToSelection(s); return s; } } return null; } /** * Selects from the bracket at the caret position to the corresponding * bracket. * @since jEdit 4.0pre2 */ public void selectToMatchingBracket() { selectToMatchingBracket(caret,false); } //}}} //{{{ selectBlock() method /** * Selects the code block surrounding the caret. * @since jEdit 2.7pre2 */ public void selectBlock() { Selection s = getSelectionAtOffset(caret); int start, end; if(s == null) start = end = caret; else { start = s.start; end = s.end; } String text = getText(0,buffer.getLength()); // We can't do the backward scan if start == 0 if(start == 0) { getToolkit().beep(); return; } // Scan backwards, trying to find a bracket String openBrackets = "([{"; String closeBrackets = ")]}"; int count = 1; char openBracket = '\0'; char closeBracket = '\0'; backward_scan: while(--start >= 0) { char c = text.charAt(start); int index = openBrackets.indexOf(c); if(index != -1) { if(--count == 0) { openBracket = c; closeBracket = closeBrackets.charAt(index); break backward_scan; } } else if(closeBrackets.indexOf(c) != -1) count++; } // Reset count count = 1; // Scan forward, matching that bracket if(openBracket == '\0') { getToolkit().beep(); return; } forward_scan: do { char c = text.charAt(end); if(c == closeBracket) { if(--count == 0) { end++; break forward_scan; } } else if(c == openBracket) count++; } while(++end < buffer.getLength()); s = new Selection.Range(start,end); if(multi) addToSelection(s); else setSelection(s); moveCaretPosition(end); } //}}} //{{{ lineInStructureScope() method /** * Returns if the specified line is contained in the currently * matched structure's scope. * @since jEdit 4.2pre3 */ public boolean lineInStructureScope(int line) { if(match == null) return false; if(match.startLine < caretLine) return line >= match.startLine && line <= caretLine; else return line <= match.endLine && line >= caretLine; } //}}} //{{{ invertSelection() method /** * Inverts the selection. * @since jEdit 4.0pre1 */ public final void invertSelection() { selectionManager.invertSelection(); } //}}} //{{{ getSelectionCount() method /** * Returns the number of selections. This can be used to test * for the existence of selections. * @since jEdit 3.2pre2 */ public int getSelectionCount() { return selectionManager.getSelectionCount(); } //}}} //{{{ getSelection() methods /** * Returns the current selection. * @since jEdit 3.2pre1 */ @Nonnull public Selection[] getSelection() { return selectionManager.getSelection(); } /** * Returns the selection with the specified index. This must be * between 0 and the return value of <code>getSelectionCount()</code>. * @since jEdit 4.3pre1 * @param index the index of the selection you want */ public Selection getSelection(int index) { return selectionManager.selection.get(index); } //}}} //{{{ getSelectionIterator() method /** * Returns the current selection. * @since jEdit 4.3pre1 */ public Iterator<Selection> getSelectionIterator() { return selectionManager.selection.iterator(); } //}}} //{{{ selectNone() method /** * Deselects everything. */ public void selectNone() { invalidateSelectedLines(); setSelection((Selection)null); } //}}} //{{{ setSelection() methods /** * Sets the selection. Nested and overlapping selections are merged * where possible. Null elements of the array are ignored. * @param selection The new selection * since jEdit 3.2pre1 */ public void setSelection(Selection[] selection) { // invalidate the old selection invalidateSelectedLines(); selectionManager.setSelection(selection); finishCaretUpdate(caretLine,NO_SCROLL,true); } /** * Sets the selection. Nested and overlapping selections are merged * where possible. * @param selection The new selection * since jEdit 3.2pre1 */ public void setSelection(Selection selection) { invalidateSelectedLines(); selectionManager.setSelection(selection); finishCaretUpdate(caretLine,NO_SCROLL,true); } //}}} //{{{ addToSelection() methods /** * Adds to the selection. Nested and overlapping selections are merged * where possible. * @param selection The new selection * since jEdit 3.2pre1 */ public void addToSelection(Selection[] selection) { invalidateSelectedLines(); selectionManager.addToSelection(selection); finishCaretUpdate(caretLine,NO_SCROLL,true); } /** * Adds to the selection. Nested and overlapping selections are merged * where possible. * @param selection The new selection * since jEdit 3.2pre1 */ public void addToSelection(Selection selection) { invalidateSelectedLines(); selectionManager.addToSelection(selection); finishCaretUpdate(caretLine,NO_SCROLL,true); } //}}} //{{{ getSelectionAtOffset() method /** * Returns the selection containing the specific offset, or <code>null</code> * if there is no selection at that offset. * @param offset The offset * @since jEdit 3.2pre1 */ public Selection getSelectionAtOffset(int offset) { return selectionManager.getSelectionAtOffset(offset); } //}}} //{{{ removeFromSelection() methods /** * Deactivates the specified selection. * @param sel The selection * @since jEdit 3.2pre1 */ public void removeFromSelection(Selection sel) { invalidateSelectedLines(); selectionManager.removeFromSelection(sel); finishCaretUpdate(caretLine,NO_SCROLL,true); } /** * Deactivates the selection at the specified offset. If there is * no selection at that offset, does nothing. * @param offset The offset * @since jEdit 3.2pre1 */ public void removeFromSelection(int offset) { Selection sel = getSelectionAtOffset(offset); if(sel == null) return; invalidateSelectedLines(); selectionManager.removeFromSelection(sel); finishCaretUpdate(caretLine,NO_SCROLL,true); } //}}} //{{{ resizeSelection() method /** * Resizes the selection at the specified offset, or creates a new * one if there is no selection at the specified offset. This is a * utility method that is mainly useful in the mouse event handler * because it handles the case of end being before offset gracefully * (unlike the rest of the selection API). * @param offset The offset * @param end The new selection end * @param extraEndVirt Only for rectangular selections - specifies how * far it extends into virtual space. * @param rect Make the selection rectangular? * @since jEdit 3.2pre1 */ public void resizeSelection(int offset, int end, int extraEndVirt, boolean rect) { Selection s = selectionManager.getSelectionAtOffset(offset); if(s != null) { invalidateLineRange(s.startLine,s.endLine); selectionManager.removeFromSelection(s); } selectionManager.resizeSelection(offset,end,extraEndVirt,rect); fireCaretEvent(); } //}}} //{{{ extendSelection() methods /** * Extends the selection at the specified offset, or creates a new * one if there is no selection at the specified offset. This is * different from resizing in that the new chunk is added to the * selection in question, instead of replacing it. * @param offset The offset * @param end The new selection end * @since jEdit 3.2pre1 */ public void extendSelection(int offset, int end) { extendSelection(offset,end,0,0); } /** * Extends the selection at the specified offset, or creates a new * one if there is no selection at the specified offset. This is * different from resizing in that the new chunk is added to the * selection in question, instead of replacing it. * @param offset The offset * @param end The new selection end * @param extraStartVirt Extra virtual space at the start * @param extraEndVirt Extra virtual space at the end * @since jEdit 4.2pre1 */ public void extendSelection(int offset, int end, int extraStartVirt, int extraEndVirt) { offset = getCharacterBoundaryAt(offset); end = getCharacterBoundaryAt(end); Selection s = getSelectionAtOffset(offset); if(s != null) { invalidateLineRange(s.startLine,s.endLine); selectionManager.removeFromSelection(s); if(offset == s.start) { offset = end; end = s.end; } else if(offset == s.end) { offset = s.start; } } if(end < offset) { int tmp = end; end = offset; offset = tmp; } if(rectangularSelectionMode) { s = new Selection.Rect(offset,end); ((Selection.Rect)s).extraStartVirt = extraStartVirt; ((Selection.Rect)s).extraEndVirt = extraEndVirt; } else s = new Selection.Range(offset,end); selectionManager.addToSelection(s); fireCaretEvent(); if(rectangularSelectionMode && extraEndVirt != 0) { int line = getLineOfOffset(end); scrollTo(line,getLineLength(line) + extraEndVirt,false); } } //}}} //{{{ getSelectedText() methods /** * Returns the text in the specified selection. * @param s The selection * @since jEdit 3.2pre1 */ public String getSelectedText(Selection s) { StringBuilder buf = new StringBuilder(s.end - s.start); s.getText(buffer,buf); return buf.toString(); } /** * Returns the text in all active selections. * @param separator The string to insert between each text chunk * (for example, a newline) * @since jEdit 3.2pre1 */ public String getSelectedText(String separator) { Selection[] sel = selectionManager.getSelection(); if(sel.length == 0) return null; StringBuilder buf = new StringBuilder(); for(int i = 0; i < sel.length; i++) { if(i != 0) buf.append(separator); sel[i].getText(buffer,buf); } return buf.toString(); } /** * Returns the text in all active selections, with a newline * between each text chunk. */ public String getSelectedText() { return getSelectedText("\n"); } //}}} //{{{ setSelectedText() methods /** * Replaces the selection with the specified text. * @param s The selection * @param selectedText The new text * @since jEdit 3.2pre1 */ public void setSelectedText(Selection s, String selectedText) { if(!isEditable()) { throw new InternalError("Text component" + " read only"); } try { buffer.beginCompoundEdit(); moveCaretPosition(s.setText(buffer,selectedText)); } // No matter what happends... stops us from leaving buffer // in a bad state finally { buffer.endCompoundEdit(); } // no no no!!!! //selectNone(); } /** * Replaces the selection at the caret with the specified text. * If there is no selection at the caret, the text is inserted at * the caret position. */ public void setSelectedText(String selectedText) { int newCaret = replaceSelection(selectedText); if(newCaret != -1) moveCaretPosition(newCaret); selectNone(); } /** * Replaces the selection at the caret with the specified text. * If there is no selection at the caret, the text is inserted at * the caret position. * @param selectedText The new selection * @param moveCaret Move caret to insertion location if necessary * @since jEdit 4.2pre5 */ public void setSelectedText(String selectedText, boolean moveCaret) { int newCaret = replaceSelection(selectedText); if(moveCaret && newCaret != -1) moveCaretPosition(newCaret); selectNone(); } //}}} //{{{ replaceSelection() method /** * Set the selection, but does not deactivate it, and does not move the * caret. * * Please use {@link #setSelectedText(String)} instead. * * @param selectedText The new selection * @return The new caret position * @since 4.3pre1 */ public int replaceSelection(String selectedText) { if(!isEditable()) throw new RuntimeException("Text component read only"); int newCaret = -1; if(getSelectionCount() == 0) { // for compatibility with older jEdit versions buffer.insert(caret,selectedText); } else { try { buffer.beginCompoundEdit(); Selection[] selection = getSelection(); for (Selection aSelection : selection) newCaret = aSelection.setText(buffer, selectedText); } finally { buffer.endCompoundEdit(); } } return newCaret; } //}}} //{{{ getSelectedLines() method /** * Returns a sorted array of line numbers on which a selection or * selections are present.<p> * * This method is the most convenient way to iterate through selected * lines in a buffer. The line numbers in the array returned by this * method can be passed as a parameter to such methods as * {@link JEditBuffer#getLineText(int)}. * * @return Non-null, non-zero sized array of line indexes. * @since jEdit 3.2pre1 */ public int[] getSelectedLines() { if(selectionManager.getSelectionCount() == 0) return new int[] { caretLine }; return selectionManager.getSelectedLines(); } //}}} //}}} //{{{ Caret //{{{ caretAutoScroll() method /** * Return if change in buffer should scroll this text area. * @since jEdit 4.3pre2 */ public boolean caretAutoScroll() { return focusedComponent == this; } //}}} //{{{ addStructureMatcher() method /** * Adds a structure matcher. * @since jEdit 4.2pre3 */ public void addStructureMatcher(StructureMatcher matcher) { structureMatchers.add(matcher); } //}}} //{{{ removeStructureMatcher() method /** * Removes a structure matcher. * @since jEdit 4.2pre3 */ public void removeStructureMatcher(StructureMatcher matcher) { structureMatchers.remove(matcher); } //}}} //{{{ getStructureMatchStart() method /** * Returns the structure element (bracket, or XML tag, etc) matching the * one before the caret. * @since jEdit 4.2pre3 */ public StructureMatcher.Match getStructureMatch() { return match; } //}}} //{{{ blinkCaret() method /** * Blinks the caret. */ public final void blinkCaret() { if(caretBlinks) { blink = !blink; invalidateLine(caretLine); } else blink = true; } //}}} //{{{ centerCaret() method /** * Centers the caret on the screen. * @since jEdit 2.7pre2 */ public void centerCaret() { int offset = getScreenLineStartOffset(visibleLines >> 1); if(offset == -1) getToolkit().beep(); else setCaretPosition(offset); } //}}} // {{{ scrollAndCenterCaret() method /** * Tries to scroll the textArea so that the caret is centered on the screen. * Sometimes gets confused by folds but at least makes the caret visible and * guesses better on subsequent attempts. * * @since jEdit 4.3pre15 */ public void scrollAndCenterCaret() { if (!getDisplayManager().isLineVisible(getCaretLine())) getDisplayManager().expandFold(getCaretLine(),true); int physicalLine = getCaretLine(); int midPhysicalLine = getPhysicalLineOfScreenLine(visibleLines >> 1); int diff = physicalLine - midPhysicalLine; setFirstLine(getFirstLine() + diff); requestFocus(); } // }}} //{{{ setCaretPosition() methods /** * Sets the caret position and deactivates the selection. * @param newCaret The caret position */ public void setCaretPosition(int newCaret) { selectNone(); moveCaretPosition(newCaret,true); } /** * Sets the caret position and deactivates the selection. * @param newCaret The caret position * @param doElectricScroll Do electric scrolling? */ public void setCaretPosition(int newCaret, boolean doElectricScroll) { selectNone(); moveCaretPosition(newCaret,doElectricScroll); } //}}} //{{{ moveCaretPosition() methods /** * Sets the caret position without deactivating the selection. * @param newCaret The caret position */ public void moveCaretPosition(int newCaret) { moveCaretPosition(newCaret,true); } /** * Sets the caret position without deactivating the selection. * @param newCaret The caret position * @param doElectricScroll Do electric scrolling? */ public void moveCaretPosition(int newCaret, boolean doElectricScroll) { moveCaretPosition(newCaret,doElectricScroll ? ELECTRIC_SCROLL : NORMAL_SCROLL); } public static final int NO_SCROLL = 0; public static final int NORMAL_SCROLL = 1; public static final int ELECTRIC_SCROLL = 2; /** * Sets the caret position without deactivating the selection. * @param newCaret The caret position * @param scrollMode The scroll mode (NO_SCROLL, NORMAL_SCROLL, or * ELECTRIC_SCROLL). * @since jEdit 4.2pre1 */ public void moveCaretPosition(int newCaret, int scrollMode) { if(newCaret < 0 || newCaret > buffer.getLength()) { throw new IllegalArgumentException("caret out of bounds: " + newCaret); } int oldCaretLine = caretLine; if(caret == newCaret) finishCaretUpdate(oldCaretLine,scrollMode,false); else { caret = getCharacterBoundaryAt(newCaret); caretLine = getLineOfOffset(caret); magicCaret = -1; finishCaretUpdate(oldCaretLine,scrollMode,true); } } //}}} //{{{ getCaretPosition() method /** * Returns a zero-based index of the caret position. */ public int getCaretPosition() { return caret; } //}}} //{{{ getCaretLine() method /** * Returns the line number containing the caret. */ public int getCaretLine() { return caretLine; } //}}} //{{{ getMagicCaretPosition() method /** * Returns an internal position used to keep the caret in one * column while moving around lines of varying lengths. * @since jEdit 4.2pre1 */ public int getMagicCaretPosition() { if(magicCaret == -1) { magicCaret = chunkCache.subregionOffsetToX( caretLine,caret - getLineStartOffset(caretLine)); } return magicCaret; } //}}} //{{{ setMagicCaretPosition() method /** * Sets the `magic' caret position. This can be used to preserve * the column position when moving up and down lines. * @param magicCaret The magic caret position * @since jEdit 4.2pre1 */ public void setMagicCaretPosition(int magicCaret) { this.magicCaret = magicCaret; } //}}} //{{{ addCaretListener() method /** * Adds a caret change listener to this text area. * @param listener The listener */ public final void addCaretListener(CaretListener listener) { listenerList.add(CaretListener.class,listener); } //}}} //{{{ removeCaretListener() method /** * Removes a caret change listener from this text area. * @param listener The listener */ public final void removeCaretListener(CaretListener listener) { listenerList.remove(CaretListener.class,listener); } //}}} //{{{ goToNextBracket() method /** * Moves the caret to the next closing bracket. * @param select true if you want to extend selection * @since jEdit 2.7pre2. */ public void goToNextBracket(boolean select) { int newCaret = -1; if(caret != buffer.getLength()) { String text = getText(caret,buffer.getLength() - caret - 1); loop: for(int i = 0; i < text.length(); i++) { switch(text.charAt(i)) { case ')': case ']': case '}': newCaret = caret + i + 1; break loop; } } } if(newCaret == -1) getToolkit().beep(); else { if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); } } //}}} //{{{ goToNextCharacter() method /** * Moves the caret to the next character. * @param select true if you want to extend selection * @since jEdit 2.7pre2. */ public void goToNextCharacter(boolean select) { Selection s = getSelectionAtOffset(caret); if(!select && s instanceof Selection.Range) { if(multi) { if(caret != s.end) { moveCaretPosition(s.end); return; } } else { setCaretPosition(s.end); return; } } int extraStartVirt, extraEndVirt; if(s instanceof Selection.Rect) { extraStartVirt = ((Selection.Rect)s).extraStartVirt; extraEndVirt = ((Selection.Rect)s).extraEndVirt; } else { extraStartVirt = 0; extraEndVirt = 0; } int newCaret = caret; if(caret == buffer.getLength()) { if(select && (rectangularSelectionMode || s instanceof Selection.Rect)) { if(s != null && caret == s.start) extraStartVirt++; else extraEndVirt++; } else { getToolkit().beep(); return; } } else if(caret == getLineEndOffset(caretLine) - 1) { if(select && (rectangularSelectionMode || s instanceof Selection.Rect)) { if(s != null && caret == s.start) extraStartVirt++; else extraEndVirt++; } else { int line = displayManager.getNextVisibleLine(caretLine); if(line == -1) { getToolkit().beep(); return; } else newCaret = getLineStartOffset(line); } } else newCaret = getNextCharacterOffset(caret); if(select) extendSelection(caret,newCaret,extraStartVirt,extraEndVirt); else if(!multi) selectNone(); moveCaretPosition(newCaret); } //}}} //{{{ goToNextLine() method /** * Move the caret to the next line. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToNextLine(boolean select) { Selection s = getSelectionAtOffset(caret); boolean rectSelect = s == null ? rectangularSelectionMode : s instanceof Selection.Rect; int magic = getMagicCaretPosition(); int newCaret = chunkCache.getBelowPosition(caretLine, caret - buffer.getLineStartOffset(caretLine),magic + 1, rectSelect && select); if(newCaret == -1) { int end = getLineEndOffset(caretLine) - 1; if(caret == end) { getToolkit().beep(); return; } else newCaret = end; } _changeLine(select, newCaret); setMagicCaretPosition(magic); }//}}} //{{{ goToNextPage() method /** * Moves the caret to the next screenful. * @param select true if you want to extend selection * @since jEdit 2.7pre2. */ public void goToNextPage(boolean select) { scrollToCaret(false); int magic = getMagicCaretPosition(); if(caretLine < displayManager.getFirstVisibleLine()) { caretLine = displayManager.getNextVisibleLine( caretLine); } int newCaret; if(getFirstLine() + getVisibleLines() >= displayManager .getScrollLineCount()) { int lastVisibleLine = displayManager .getLastVisibleLine(); newCaret = getLineEndOffset(lastVisibleLine) - 1; } else { int caretScreenLine = getScreenLineOfOffset(caret); scrollDownPage(); newCaret = xToScreenLineOffset(caretScreenLine, magic,true); } if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret,false); setMagicCaretPosition(magic); } //}}} //{{{ goToNextParagraph() method /** * Moves the caret to the start of the next paragraph. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToNextParagraph(boolean select) { int lineNo = getCaretLine(); int newCaret = getBufferLength(); boolean foundBlank = false; final Segment lineSegment = new Segment(); loop: for(int i = lineNo + 1; i < getLineCount(); i++) { if(!displayManager.isLineVisible(i)) continue; getLineText(i,lineSegment); for(int j = 0; j < lineSegment.count; j++) { switch(lineSegment.array[lineSegment.offset + j]) { case ' ': case '\t': break; default: if(foundBlank) { newCaret = getLineStartOffset(i); break loop; } else continue loop; } } foundBlank = true; } if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); } //}}} //{{{ goToNextWord() methods /** * Moves the caret to the start of the next word. * Note that if the "view.eatWhitespace" boolean propery is false, * this method moves the caret to the end of the current word instead. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToNextWord(boolean select) { goToNextWord(select,false); } /** * Moves the caret to the start of the next word. * @since jEdit 4.1pre5 */ public void goToNextWord(boolean select, boolean eatWhitespace) { if (buffer.isLoading()) return; int lineStart = getLineStartOffset(caretLine); int newCaret = caret - lineStart; String lineText = getLineText(caretLine); if(newCaret == lineText.length()) { int nextLine = displayManager.getNextVisibleLine(caretLine); if(nextLine == -1) { getToolkit().beep(); return; } newCaret = getLineStartOffset(nextLine); } else { String noWordSep = buffer.getStringProperty("noWordSep"); boolean camelCasedWords = buffer.getBooleanProperty("camelCasedWords"); newCaret = TextUtilities.findWordEnd(lineText, newCaret + 1,noWordSep,true,camelCasedWords, eatWhitespace); newCaret += lineStart; } if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); } //}}} //{{{ goToPrevBracket() method /** * Moves the caret to the previous bracket. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToPrevBracket(boolean select) { String text = getText(0,caret); int newCaret = -1; loop: for(int i = getCaretPosition() - 1; i >= 0; i--) { switch(text.charAt(i)) { case '(': case '[': case '{': newCaret = i; break loop; } } if(newCaret == -1) getToolkit().beep(); else { if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); } } //}}} //{{{ goToPrevCharacter() method /** * Moves the caret to the previous character. * @param select true if you want to extend selection * @since jEdit 2.7pre2. */ public void goToPrevCharacter(boolean select) { Selection s = getSelectionAtOffset(caret); if(!select && s instanceof Selection.Range) { if(multi) { if(caret != s.start) { moveCaretPosition(s.start); return; } } else { setCaretPosition(s.start); return; } } if(caret == 0) { getToolkit().beep(); return; } int extraStartVirt = 0; int extraEndVirt = 0; int newCaret = caret; if(select && caret == getLineEndOffset(caretLine) - 1) { if(s instanceof Selection.Rect) { extraStartVirt = ((Selection.Rect)s).extraStartVirt; extraEndVirt = ((Selection.Rect)s).extraEndVirt; if(caret == s.start) { if(extraStartVirt == 0) newCaret = getPrevCharacterOffset(caret); else extraStartVirt--; } else { if(extraEndVirt == 0) newCaret = getPrevCharacterOffset(caret); else extraEndVirt--; } } else newCaret = getPrevCharacterOffset(caret); } else if(caret == getLineStartOffset(caretLine)) { int line = displayManager.getPrevVisibleLine(caretLine); if(line == -1) { getToolkit().beep(); return; } newCaret = getLineEndOffset(line) - 1; } else newCaret = getPrevCharacterOffset(caret); if(select) extendSelection(caret,newCaret,extraStartVirt,extraEndVirt); else if(!multi) selectNone(); moveCaretPosition(newCaret); } //}}} //{{{ goToPrevLine() method /** * Moves the caret to the previous line. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToPrevLine(boolean select) { Selection s = getSelectionAtOffset(caret); boolean rectSelect = s == null ? rectangularSelectionMode : s instanceof Selection.Rect; int magic = getMagicCaretPosition(); int newCaret = chunkCache.getAbovePosition(caretLine, caret - buffer.getLineStartOffset(caretLine),magic + 1, rectSelect && select); if(newCaret == -1) { int start = getLineStartOffset(caretLine); if(caret == start) { getToolkit().beep(); return; } else newCaret = start; } _changeLine(select, newCaret); setMagicCaretPosition(magic); } //}}} //{{{ goToPrevPage() method /** * Moves the caret to the previous screenful. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToPrevPage(boolean select) { scrollToCaret(false); int magic = getMagicCaretPosition(); if(caretLine < displayManager.getFirstVisibleLine()) { caretLine = displayManager.getNextVisibleLine( caretLine); } int newCaret; if(getFirstLine() == 0) { int firstVisibleLine = displayManager .getFirstVisibleLine(); newCaret = getLineStartOffset(firstVisibleLine); } else { int caretScreenLine = getScreenLineOfOffset(caret); scrollUpPage(); newCaret = xToScreenLineOffset(caretScreenLine, magic,true); } if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret,false); setMagicCaretPosition(magic); } //}}} //{{{ goToPrevParagraph() method /** * Moves the caret to the start of the previous paragraph. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToPrevParagraph(boolean select) { int lineNo = caretLine; int newCaret = 0; boolean foundBlank = false; final Segment lineSegment = new Segment(); loop: for(int i = lineNo - 1; i >= 0; i--) { if(!displayManager.isLineVisible(i)) continue; getLineText(i,lineSegment); for(int j = 0; j < lineSegment.count; j++) { switch(lineSegment.array[lineSegment.offset + j]) { case ' ': case '\t': break; default: if(foundBlank) { newCaret = getLineEndOffset(i) - 1; break loop; } else continue loop; } } foundBlank = true; } if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); } //}}} //{{{ goToPrevWord() method /** * Moves the caret to the start of the previous word. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToPrevWord(boolean select) { goToPrevWord(select,false); } //}}} //{{{ goToPrevWord() method /** * Moves the caret to the start of the previous word. * @param eatWhitespace If true, will eat whitespace * @since jEdit 4.1pre5 */ public void goToPrevWord(boolean select, boolean eatWhitespace) { goToPrevWord(select,eatWhitespace,false); } //}}} //{{{ goToPrevWord() method /** * Moves the caret to the start of the previous word. * @param eatWhitespace If true, will eat whitespace * @param eatOnlyAfterWord Eat only whitespace after a word, * in effect this goes to actual word starts even if eating * @since jEdit 4.4pre1 */ public void goToPrevWord(boolean select, boolean eatWhitespace, boolean eatOnlyAfterWord) { if (buffer.isLoading()) return; int lineStart = getLineStartOffset(caretLine); int newCaret = caret - lineStart; String lineText = getLineText(caretLine); if(newCaret == 0) { if(lineStart == 0) { getToolkit().beep(); return; } else { int prevLine = displayManager.getPrevVisibleLine(caretLine); if(prevLine == -1) { getToolkit().beep(); return; } newCaret = getLineEndOffset(prevLine) - 1; } } else { String noWordSep = buffer.getStringProperty("noWordSep"); boolean camelCasedWords = buffer.getBooleanProperty("camelCasedWords"); newCaret = TextUtilities.findWordStart(lineText, newCaret - 1,noWordSep,true,camelCasedWords,eatWhitespace, eatOnlyAfterWord); newCaret += lineStart; } if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); } //}}} //{{{ home() method /** * A "dumb home" action which only has 2 states: * start of the whitespace or start of line * @param select true if we also want to select from the cursor * @since jedit 4.3pre18 */ public void home(boolean select) { switch(getInputHandler().getLastActionCount() % 2) { case 1: goToStartOfWhiteSpace(select); break; default: goToStartOfLine(select); break; } } //}}} //{{{ end() method /** * a dumb end action which only has 2 states: * end of whitespace or end of line * @param select true if we also want to select from the cursor * @since jedit 4.3pre18 */ public void end(boolean select) { switch(getInputHandler().getLastActionCount() % 2) { case 1: goToEndOfWhiteSpace(select); break; default: goToEndOfLine(select); break; } } //}}} //{{{ smartHome() method /** * On subsequent invocations, first moves the caret to the first * non-whitespace character of the line, then the beginning of the * line, then to the first visible line. * @param select true if you want to extend selection * @since jEdit 4.3pre7 */ public void smartHome(boolean select) { switch(getInputHandler().getLastActionCount()) { case 1: goToStartOfWhiteSpace(select); break; case 2: goToStartOfLine(select); break; default: //case 3: goToFirstVisibleLine(select); break; } } //}}} //{{{ smartEnd() method /** * Has 4 states based on # of invocations: * 1. last character of code (before inline comment) * 2. last non whitespace character of the line * 3. end of line * 4. end of last visible line * @param select true if you want to extend selection * @since jEdit 4.3pre18 */ public void smartEnd(boolean select) { switch(getInputHandler().getLastActionCount()) { case 1: int pos = getCaretPosition(); goToEndOfCode(select); int npos = getCaretPosition(); if (npos == pos) goToEndOfWhiteSpace(select); break; case 2: goToEndOfWhiteSpace(select); break; case 3: goToEndOfLine(select); break; default: //case 4: goToLastVisibleLine(select); break; } } //}}} //{{{ goToStartOfLine() method /** * Moves the caret to the beginning of the current line. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToStartOfLine(boolean select) { Selection s = getSelectionAtOffset(caret); int line = select || s == null ? caretLine : s.startLine; int newCaret = getLineStartOffset(line); if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); } //}}} //{{{ goToEndOfLine() method /** * Moves the caret to the end of the current line. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToEndOfLine(boolean select) { Selection s = getSelectionAtOffset(caret); int line = select || s == null ? caretLine : s.endLine; int newCaret = getLineEndOffset(line) - 1; if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); // so that end followed by up arrow will always put caret at // the end of the previous line, for example //setMagicCaretPosition(Integer.MAX_VALUE); } //}}} //{{{ goToEndOfCode() method /** * Moves the caret to the end of the code present on the current line, before the comments and whitespace. * @param select true if you want to extend selection * @since jEdit 4.3pre18 */ public void goToEndOfCode(boolean select) { int line = getCaretLine(); DefaultTokenHandler tokenHandler = new DefaultTokenHandler(); buffer.markTokens(line,tokenHandler); Token token = tokenHandler.getTokens(); char[] txt = getLineText(line).toCharArray(); // replace comments with whitespace to find endOfCode: while(true) { if( token.id == Token.COMMENT1 || token.id == Token.COMMENT2 || token.id == Token.COMMENT3 || token.id == Token.COMMENT4) { for(int i=token.offset; i<token.offset+token.length; i++) { txt[i] = ' '; } } if(token.next == null) break; token = token.next; } int newCaret = getLineLength(line) - StandardUtilities.getTrailingWhiteSpace( new String(txt) ); newCaret += getLineStartOffset(line); if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); } //}}} //{{{ goToStartOfWhiteSpace() method /** * Moves the caret to the first non-whitespace character of the current * line. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToStartOfWhiteSpace(boolean select) { if (buffer.isLoading()) return; Selection s = getSelectionAtOffset(caret); int line, offset; if(select || s == null) { line = caretLine; offset = caret - buffer.getLineStartOffset(line); } else { line = s.startLine; offset = s.start - buffer.getLineStartOffset(line); } int firstIndent = chunkCache.getSubregionStartOffset(line,offset); if(firstIndent == getLineStartOffset(line)) { firstIndent = StandardUtilities.getLeadingWhiteSpace(getLineText(line)); if(firstIndent == getLineLength(line)) firstIndent = 0; firstIndent += getLineStartOffset(line); } if(select) extendSelection(caret,firstIndent); else if(!multi) selectNone(); moveCaretPosition(firstIndent); } //}}} //{{{ goToEndOfWhiteSpace() method /** * Moves the caret to the last non-whitespace character of the current * line. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToEndOfWhiteSpace(boolean select) { if (buffer.isLoading()) return; Selection s = getSelectionAtOffset(caret); int line, offset; if(select || s == null) { line = caretLine; offset = caret - getLineStartOffset(line); } else { line = s.endLine; offset = s.end - getLineStartOffset(line); } int lastIndent = chunkCache.getSubregionEndOffset(line,offset); if(lastIndent == getLineEndOffset(line)) { lastIndent = getLineLength(line) - StandardUtilities.getTrailingWhiteSpace(getLineText(line)); if(lastIndent == 0) lastIndent = getLineLength(line); lastIndent += getLineStartOffset(line); } else { lastIndent--; } if(select) extendSelection(caret,lastIndent); else if(!multi) selectNone(); moveCaretPosition(lastIndent); } //}}} //{{{ goToFirstVisibleLine() method /** * Moves the caret to the first visible line. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToFirstVisibleLine(boolean select) { int firstVisibleLine = getFirstLine() == 0 ? 0 : electricScroll; int firstVisible = getScreenLineStartOffset(firstVisibleLine); if(firstVisible == -1) { firstVisible = getLineStartOffset(displayManager .getFirstVisibleLine()); } if(select) extendSelection(caret,firstVisible); else if(!multi) selectNone(); moveCaretPosition(firstVisible); } //}}} //{{{ goToLastVisibleLine() method /** * Moves the caret to the last visible line. * @param select true if you want to extend selection * @since jEdit 2.7pre2 */ public void goToLastVisibleLine(boolean select) { int lastVisible; if(getFirstLine() + visibleLines >= displayManager.getScrollLineCount()) { lastVisible = getLineEndOffset(displayManager .getLastVisibleLine()) - 1; } else { lastVisible = visibleLines - electricScroll - 1; if(lastLinePartial) lastVisible--; if(lastVisible < 0) lastVisible = 0; lastVisible = getScreenLineEndOffset(lastVisible) - 1; if(lastVisible == -1) { lastVisible = getLineEndOffset(displayManager .getLastVisibleLine()) - 1; } } if(select) extendSelection(caret,lastVisible); else if(!multi) selectNone(); moveCaretPosition(lastVisible); } //}}} //{{{ goToBufferStart() method /** * Moves the caret to the beginning of the buffer. * @param select true if you want to extend selection * @since jEdit 4.0pre3 */ public void goToBufferStart(boolean select) { int start = buffer.getLineStartOffset( displayManager.getFirstVisibleLine()); if(select) extendSelection(caret,start); else if(!multi) selectNone(); moveCaretPosition(start); } //}}} //{{{ goToBufferEnd() method /** * Moves the caret to the end of the buffer. * @param select true if you want to extend selection * @since jEdit 4.0pre3 */ public void goToBufferEnd(boolean select) { int end = buffer.getLineEndOffset( displayManager.getLastVisibleLine()) - 1; if(select) extendSelection(caret,end); else if(!multi) selectNone(); moveCaretPosition(end); } //}}} //{{{ goToMatchingBracket() method /** * Moves the caret to the bracket matching the one before the caret. * @since jEdit 2.7pre3 */ public void goToMatchingBracket() { if(getLineLength(caretLine) != 0) { int dot = caret - getLineStartOffset(caretLine); int bracket = TextUtilities.findMatchingBracket( buffer,caretLine,Math.max(0,dot - 1)); if(bracket != -1) { selectNone(); moveCaretPosition(bracket + 1,false); return; } } getToolkit().beep(); } //}}} //}}} //{{{ User input //{{{ userInput() method /** * Handles the insertion of the specified character. It performs the * following operations above and beyond simply inserting the text: * <ul> * <li>Inserting a TAB with a selection will shift to the right * <li>Inserting a BACK_SPACE or a DELETE will remove a character * <li>Inserting an indent open/close bracket will re-indent the current * line as necessary * </ul> * * @param ch The character * @see #setSelectedText(String) * @see #isOverwriteEnabled() * @since jEdit 4.3pre7 */ public void userInput(char ch) { if(!isEditable()) { getToolkit().beep(); return; } getPainter().hideCursor(); switch(ch) { case '\t': userInputTab(); break; case '\b': backspace(); break; case '\u007F': delete(); break; default: String str = String.valueOf(ch); if(getSelectionCount() == 0) { if(!doWordWrap(ch == ' ')) { boolean indent = buffer.isElectricKey(ch, caretLine) && "full".equals(buffer.getStringProperty("autoIndent")) && /* if the line is not manually indented */ (buffer.getCurrentIndentForLine(caretLine, null) == buffer.getIdealIndentForLine(caretLine)); insert(str,indent); } } else replaceSelection(str); break; } } //}}} //{{{ isOverwriteEnabled() method /** * Returns true if overwrite mode is enabled, false otherwise. */ public final boolean isOverwriteEnabled() { return overwrite; } //}}} //{{{ setOverwriteEnabled() method /** * Sets overwrite mode. */ public final void setOverwriteEnabled(boolean overwrite) { blink = true; caretTimer.restart(); this.overwrite = overwrite; invalidateLine(caretLine); fireStatusChanged(StatusListener.OVERWRITE_CHANGED,overwrite); } //}}} //{{{ toggleOverwriteEnabled() method /** * Toggles overwrite mode. * @since jEdit 2.7pre2 */ public final void toggleOverwriteEnabled() { setOverwriteEnabled(!overwrite); } //}}} //{{{ backspace() method /** * Deletes the character before the caret, or the selection, if one is * active. * @since jEdit 2.7pre2 */ public void backspace() { delete(false); } //}}} //{{{ backspaceWord() methods /** * Deletes the word before the caret. * @since jEdit 2.7pre2 */ public void backspaceWord() { backspaceWord(false); } /** * Deletes the word before the caret. * @param eatWhitespace If true, will eat whitespace * @since jEdit 4.2pre5 */ public void backspaceWord(boolean eatWhitespace) { backspaceWord(eatWhitespace,false); } //}}} //{{{ backspaceWord() method /** * Deletes the word before the caret. * @param eatWhitespace If true, will eat whitespace * @param eatOnlyAfterWord Eat only whitespace after a word, * in effect this goes to actual word starts even if eating * @since jEdit 4.4pre1 */ public void backspaceWord(boolean eatWhitespace, boolean eatOnlyAfterWord) { if(!buffer.isEditable()) { getToolkit().beep(); return; } if(getSelectionCount() != 0) { setSelectedText(""); return; } int lineStart = getLineStartOffset(caretLine); int _caret = caret - lineStart; String lineText = getLineText(caretLine); if(_caret == 0) { if(lineStart == 0) { getToolkit().beep(); return; } _caret--; } else { String noWordSep = buffer.getStringProperty("noWordSep"); boolean camelCasedWords = buffer.getBooleanProperty("camelCasedWords"); _caret = TextUtilities.findWordStart(lineText,_caret-1, noWordSep,true,camelCasedWords,eatWhitespace, eatOnlyAfterWord); } buffer.remove(_caret + lineStart, caret - (_caret + lineStart)); } //}}} //{{{ delete() method /** * Deletes the character after the caret. * @since jEdit 2.7pre2 */ public void delete() { delete(true); } //}}} //{{{ deleteToEndOfLine() method /** * Deletes from the caret to the end of the current line. * @since jEdit 2.7pre2 */ public void deleteToEndOfLine() { if(!buffer.isEditable()) { getToolkit().beep(); return; } buffer.remove(caret,getLineEndOffset(caretLine) - caret - 1); } //}}} //{{{ deleteLine() method /** * Deletes the line containing the caret. * @since jEdit 2.7pre2 */ public void deleteLine() { if(!buffer.isEditable()) { getToolkit().beep(); return; } int x = chunkCache.subregionOffsetToX(caretLine,caret - getLineStartOffset(caretLine)); int[] lines = getSelectedLines(); try { buffer.beginCompoundEdit(); for (int i = lines.length - 1; i >= 0; i--) { int start = getLineStartOffset(lines[i]); int end = getLineEndOffset(lines[i]); if (end > buffer.getLength()) { if (start != 0) start--; end--; } buffer.remove(start,end - start); } } finally { buffer.endCompoundEdit(); } int lastLine = displayManager.getLastVisibleLine(); if(caretLine == lastLine) { int offset = chunkCache.xToSubregionOffset(lastLine,0,x,true); setCaretPosition(buffer.getLineStartOffset(lastLine) + offset); } else { int offset = chunkCache.xToSubregionOffset(caretLine,0,x,true); setCaretPosition(getLineStartOffset(caretLine) + offset); } } //}}} //{{{ deleteParagraph() method /** * Deletes the paragraph containing the caret. * @since jEdit 2.7pre2 */ public void deleteParagraph() { if(!buffer.isEditable()) { getToolkit().beep(); return; } // find the beginning of the paragraph. int start = 0; for(int i = caretLine - 1; i >= 0; i--) { if (lineContainsSpaceAndTabs(i)) { start = getLineStartOffset(i); break; } } // Find the end of the paragraph int end = buffer.getLength(); for(int i = caretLine + 1; i < getLineCount(); i++) { //if(!displayManager.isLineVisible(i)) // continue loop; if (lineContainsSpaceAndTabs(i)) { end = getLineEndOffset(i) - 1; break; } } buffer.remove(start,end - start); } //}}} //{{{ deleteToStartOfLine() method /** * Deletes from the caret to the beginning of the current line. * @since jEdit 2.7pre2 */ public void deleteToStartOfLine() { if(!buffer.isEditable()) { getToolkit().beep(); return; } buffer.remove(getLineStartOffset(caretLine), caret - getLineStartOffset(caretLine)); } //}}} //{{{ deleteWord() methods /** * Deletes the word in front of the caret. * @since jEdit 2.7pre2 */ public void deleteWord() { deleteWord(false); } /** * Deletes the word in front of the caret. * . * @param eatWhitespace If true, will eat whitespace * @since jEdit 4.2pre5 */ public void deleteWord(boolean eatWhitespace) { if(!buffer.isEditable()) { getToolkit().beep(); return; } if(getSelectionCount() != 0) { setSelectedText(""); return; } int lineStart = getLineStartOffset(caretLine); int _caret = caret - lineStart; String lineText = getLineText(caretLine); if(_caret == lineText.length()) { if(lineStart + _caret == buffer.getLength()) { getToolkit().beep(); return; } _caret++; } else { String noWordSep = buffer.getStringProperty("noWordSep"); boolean camelCasedWords = buffer.getBooleanProperty("camelCasedWords"); _caret = TextUtilities.findWordEnd(lineText, _caret+1,noWordSep,true,camelCasedWords,eatWhitespace); } buffer.remove(caret,(_caret + lineStart) - caret); } //}}} //{{{ isMultipleSelectionEnabled() method /** * Returns if multiple selection is enabled. * @since jEdit 3.2pre1 */ public final boolean isMultipleSelectionEnabled() { return multi; } //}}} //{{{ toggleMultipleSelectionEnabled() method /** * Toggles multiple selection. * @since jEdit 3.2pre1 */ public final void toggleMultipleSelectionEnabled() { setMultipleSelectionEnabled(!multi); } //}}} //{{{ setMultipleSelectionEnabled() method /** * Set multiple selection on or off according to the value of * <code>multi</code>. This only affects the ability to * make multiple selections in the user interface; macros and plugins * can manipulate them regardless of the setting of this flag. In fact, * in most cases, calling this method should not be necessary. * * @param multi Should multiple selection be enabled? * @since jEdit 3.2pre1 */ public final void setMultipleSelectionEnabled(boolean multi) { this.multi = multi; fireStatusChanged(StatusListener.MULTI_SELECT_CHANGED,multi); painter.repaint(); } //}}} //{{{ isRectangularSelectionEnabled() method /** * Returns if rectangular selection is enabled. * @since jEdit 4.2pre1 */ public final boolean isRectangularSelectionEnabled() { return rectangularSelectionMode; } //}}} //{{{ toggleRectangularSelectionEnabled() method /** * Toggles rectangular selection. * @since jEdit 4.2pre1 */ public final void toggleRectangularSelectionEnabled() { setRectangularSelectionEnabled(!rectangularSelectionMode); if(getSelectionCount() == 1) { Selection s = getSelection(0); removeFromSelection(s); if(rectangularSelectionMode) { addToSelection(new Selection.Rect( s.getStart(),s.getEnd())); } else { addToSelection(new Selection.Range( s.getStart(),s.getEnd())); } } } //}}} //{{{ setRectangularSelectionEnabled() method /** * Set rectangular selection on or off according to the value of * <code>rectangularSelectionMode</code>. This only affects the ability * to make multiple selections from the keyboard. A rectangular * selection can always be created by dragging with the mouse by holding * down <b>Control</b>, regardless of the state of this flag. * * @param rectangularSelectionMode Should rectangular selection be * enabled? * @since jEdit 4.2pre1 */ public final void setRectangularSelectionEnabled( boolean rectangularSelectionMode) { this.rectangularSelectionMode = rectangularSelectionMode; fireStatusChanged(StatusListener.RECT_SELECT_CHANGED, rectangularSelectionMode); painter.repaint(); } //}}} //}}} //{{{ Folding //{{{ goToParentFold() method /** * Moves the caret to the fold containing the one at the caret * position. * @since jEdit 4.0pre3 */ public void goToParentFold() { int line = -1; int level = buffer.getFoldLevel(caretLine); for(int i = caretLine - 1; i >= 0; i--) { if(buffer.getFoldLevel(i) < level) { line = i; break; } } if(line == -1) { getToolkit().beep(); return; } int magic = getMagicCaretPosition(); int newCaret = buffer.getLineStartOffset(line) + chunkCache.xToSubregionOffset(line,0,magic + 1,true); if(!multi) selectNone(); moveCaretPosition(newCaret); setMagicCaretPosition(magic); } //}}} //{{{ goToNextFold() method /** * Moves the caret to the next fold. * @param select true if you want to extend selection * @since jEdit 4.0pre3 */ public void goToNextFold(boolean select) { int nextFold = -1; for(int i = caretLine + 1; i < buffer.getLineCount(); i++) { if(buffer.isFoldStart(i) && displayManager.isLineVisible(i)) { nextFold = i; break; } } if(nextFold == -1) { getToolkit().beep(); return; } int magic = getMagicCaretPosition(); int newCaret = buffer.getLineStartOffset(nextFold) + chunkCache.xToSubregionOffset(nextFold,0,magic + 1,true); if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); setMagicCaretPosition(magic); } //}}} //{{{ goToPrevFold() method /** * Moves the caret to the previous fold. * @param select true if you want to extend selection * @since jEdit 4.0pre3 */ public void goToPrevFold(boolean select) { int prevFold = -1; for(int i = caretLine - 1; i >= 0; i--) { if(buffer.isFoldStart(i) && displayManager.isLineVisible(i)) { prevFold = i; break; } } if(prevFold == -1) { getToolkit().beep(); return; } int magic = getMagicCaretPosition(); int newCaret = buffer.getLineStartOffset(prevFold) + chunkCache.xToSubregionOffset(prevFold,0,magic + 1,true); if(select) extendSelection(caret,newCaret); else if(!multi) selectNone(); moveCaretPosition(newCaret); setMagicCaretPosition(magic); } //}}} //{{{ collapseFold() methods /** * Like {@link DisplayManager#collapseFold(int)}, but * also moves the caret to the first line of the fold. * @since jEdit 4.0pre3 */ public void collapseFold() { collapseFold(caretLine); } /** * Like {@link DisplayManager#collapseFold(int)}, but * also moves the caret to the first line of the fold. * @param line the physical line index of the fold that we want to collapse * @since jEdit 4.3pre7 */ public void collapseFold(int line) { displayManager.collapseFold(line); } //}}} //{{{ expandFold() method /** * Like {@link DisplayManager#expandFold(int,boolean)}, but * also moves the caret to the first sub-fold. * @param fully If true, all subfolds will also be expanded * @since jEdit 4.0pre3 */ public void expandFold(boolean fully) { int x = chunkCache.subregionOffsetToX(caretLine, caret - getLineStartOffset(caretLine)); int line = displayManager.expandFold(caretLine,fully); if(!fully && line != -1) { if(!multi) selectNone(); moveCaretPosition(getLineStartOffset(line) + chunkCache.xToSubregionOffset(line,0,x,true)); } } //}}} //{{{ selectFold() methods /** * Selects the fold that contains the caret line number. * @since jEdit 3.1pre3 */ public void selectFold() { selectFold(caretLine); } /** * Selects the fold that contains the specified line number. * @param line The line number * @since jEdit 4.0pre1 */ public void selectFold(int line) { int[] lines = buffer.getFoldAtLine(line); int newCaret = getLineEndOffset(lines[1]) - 1; Selection s = new Selection.Range(getLineStartOffset(lines[0]),newCaret); if(multi) addToSelection(s); else setSelection(s); moveCaretPosition(newCaret); } //}}} //{{{ narrowToFold() method /** * Hides all lines except those in the fold containing the caret. * @since jEdit 4.0pre1 */ public void narrowToFold() { int[] lines = buffer.getFoldAtLine(caretLine); if(lines[0] == 0 && lines[1] == buffer.getLineCount() - 1) getToolkit().beep(); else displayManager.narrow(lines[0],lines[1]); } //}}} //{{{ narrowToSelection() method /** * Hides all lines except those in the selection. * @since jEdit 4.0pre1 */ public void narrowToSelection() { if(getSelectionCount() != 1) { getToolkit().beep(); return; } Selection sel = getSelection(0); displayManager.narrow(sel.getStartLine(),sel.getEndLine()); selectNone(); } //}}} //{{{ addExplicitFold() method /** * Surrounds the selection with explicit fold markers. * @throws TextAreaException an exception thrown if the folding mode is * not explicit * @since jEdit 4.0pre3 */ public void addExplicitFold() throws TextAreaException { if(!buffer.isEditable()) { getToolkit().beep(); return; } if(!"explicit".equals(buffer.getStringProperty("folding"))) { throw new TextAreaException("folding-not-explicit"); } try { buffer.beginCompoundEdit(); if (getSelectionCount() == 0) { int caretBack = addExplicitFold(caret, caret, caretLine, caretLine); setCaretPosition(caret - caretBack); } else { Selection[] selections = getSelection(); Selection selection = null; int caretBack = 0; for (Selection selection1 : selections) { selection = selection1; caretBack = addExplicitFold(selection.start, selection.end, selection.startLine, selection.endLine); } // Selection cannot be null because there is at least 1 selection assert selection != null; setCaretPosition(selection.start - caretBack, false); } } finally { buffer.endCompoundEdit(); } } //}}} //}}} //{{{ Text editing //{{{ lineComment() method /** * Prepends each line of the selection with the line comment string. * @since jEdit 3.2pre1 */ public void lineComment() { if(!buffer.isEditable()) { getToolkit().beep(); return; } String comment = buffer.getContextSensitiveProperty(caret,"lineComment"); if(comment == null || comment.length() == 0) { rangeLineComment(); return; } comment += ' '; buffer.beginCompoundEdit(); int[] lines = getSelectedLines(); try { for (int line : lines) { String text = getLineText(line); buffer.insert(getLineStartOffset(line) + StandardUtilities.getLeadingWhiteSpace(text), comment); } } finally { buffer.endCompoundEdit(); } selectNone(); } //}}} //{{{ rangeComment() method /** * Adds comment start and end strings to the beginning and end of the * selection. * @since jEdit 3.2pre1 */ public void rangeComment() { String commentStart = buffer.getContextSensitiveProperty(caret,"commentStart"); String commentEnd = buffer.getContextSensitiveProperty(caret,"commentEnd"); if(!buffer.isEditable() || commentStart == null || commentEnd == null || commentStart.length() == 0 || commentEnd.length() == 0) { getToolkit().beep(); return; } commentStart += ' '; commentEnd = ' ' + commentEnd; try { buffer.beginCompoundEdit(); Selection[] selection = getSelection(); if(selection.length == 0) { int oldCaret = caret; buffer.insert(caret,commentStart); buffer.insert(caret,commentEnd); setCaretPosition(oldCaret + commentStart.length()); } for (Selection s : selection) { if (s instanceof Selection.Range) { buffer.insert(s.start, commentStart); buffer.insert(s.end, commentEnd); } else if (s instanceof Selection.Rect) { Selection.Rect rect = (Selection.Rect) s; int start = rect.getStartColumn(buffer); int end = rect.getEndColumn(buffer); for (int j = s.startLine; j <= s.endLine; j++) { buffer.insertAtColumn(j, end, commentEnd); buffer.insertAtColumn(j, start, commentStart); } } } selectNone(); } finally { buffer.endCompoundEdit(); } } //}}} //{{{ formatParagraph() method /** * Formats the paragraph containing the caret. * @since jEdit 2.7pre2 */ public void formatParagraph() throws TextAreaException { if(!buffer.isEditable()) { getToolkit().beep(); return; } if(maxLineLen <= 0) { throw new TextAreaException("format-maxlinelen"); } Selection[] selection = getSelection(); if(selection.length != 0) { buffer.beginCompoundEdit(); for (Selection s : selection) { setSelectedText(s, TextUtilities.format(getSelectedText(s), maxLineLen, buffer.getTabSize())); } buffer.endCompoundEdit(); } else { int lineNo = getCaretLine(); int start = 0, end = buffer.getLength(); for(int i = lineNo - 1; i >= 0; i--) { if (lineContainsSpaceAndTabs(i)) { start = getLineEndOffset(i); break; } } for(int i = lineNo + 1; i < getLineCount(); i++) { if (lineContainsSpaceAndTabs(i)) { end = getLineStartOffset(i) - 1; break; } } try { buffer.beginCompoundEdit(); String text = buffer.getText(start,end - start); int offset = getCaretPosition() - start; int noSpaceOffset = TextUtilities.indexIgnoringWhitespace( text,offset); buffer.remove(start,end - start); text = TextUtilities.format( text,maxLineLen,buffer.getTabSize()); buffer.insert(start,text); int caretPos = start; if (text.length() != 0) { caretPos += Math.min(text.length(), TextUtilities.ignoringWhitespaceIndex( text,noSpaceOffset)); } moveCaretPosition(caretPos); } finally { buffer.endCompoundEdit(); } } } //}}} //{{{ spacesToTabs() method /** * Converts spaces to tabs in the selection. * @since jEdit 2.7pre2 */ public void spacesToTabs() { Selection[] selection = getSelection(); if(!buffer.isEditable()) { getToolkit().beep(); return; } buffer.beginCompoundEdit(); if(selection.length == 0) { setText(TextUtilities.spacesToTabs( getText(), buffer.getTabSize())); } else { for (Selection s : selection) { setSelectedText(s, TextUtilities.spacesToTabs( getSelectedText(s), buffer.getTabSize())); } } buffer.endCompoundEdit(); } //}}} //{{{ tabsToSpaces() method /** * Converts tabs to spaces in the selection. * @since jEdit 2.7pre2 */ public void tabsToSpaces() { Selection[] selection = getSelection(); if(!buffer.isEditable()) { getToolkit().beep(); return; } buffer.beginCompoundEdit(); if(selection.length == 0) { setText(TextUtilities.tabsToSpaces( getText(), buffer.getTabSize())); } else { for (Selection s : selection) { setSelectedText(s, TextUtilities.tabsToSpaces( getSelectedText(s), buffer.getTabSize())); } } buffer.endCompoundEdit(); } //}}} //{{{ toUpperCase() method /** * Converts the selected text to upper case. * @since jEdit 2.7pre2 */ public void toUpperCase() { if(!buffer.isEditable()) { getToolkit().beep(); return; } Selection[] selection = getSelection(); int caret = -1; if (selection.length == 0) { caret = getCaretPosition(); selectWord(); selection = getSelection(); } if (selection.length == 0) { if (caret != -1) setCaretPosition(caret); getToolkit().beep(); return; } buffer.beginCompoundEdit(); for (Selection s : selection) setSelectedText(s, getSelectedText(s).toUpperCase()); buffer.endCompoundEdit(); if (caret != -1) setCaretPosition(caret); } //}}} //{{{ toLowerCase() method /** * Converts the selected text to lower case. * @since jEdit 2.7pre2 */ public void toLowerCase() { if(!buffer.isEditable()) { getToolkit().beep(); return; } Selection[] selection = getSelection(); int caret = -1; if (selection.length == 0) { caret = getCaretPosition(); selectWord(); selection = getSelection(); } if (selection.length == 0) { if (caret != -1) setCaretPosition(caret); getToolkit().beep(); return; } buffer.beginCompoundEdit(); for (Selection s : selection) setSelectedText(s, getSelectedText(s).toLowerCase()); buffer.endCompoundEdit(); if (caret != -1) setCaretPosition(caret); } //}}} //{{{ removeTrailingWhiteSpace() method /** * Removes trailing whitespace from all lines in the selection. * @since jEdit 2.7pre2 */ public void removeTrailingWhiteSpace() { if(!buffer.isEditable()) getToolkit().beep(); else { buffer.removeTrailingWhiteSpace(getSelectedLines()); } } //}}} //{{{ insertEnterAndIndent() method /** * Inserts a line break and indents the new line. Moves the caret to * the first non-whitespace character of the new line. If the newline * character is an electric key the current line will also be * re-indented. */ public void insertEnterAndIndent() { if(!isEditable()) getToolkit().beep(); else { String autoIndent = buffer.getStringProperty("autoIndent"); if ("full".equals(autoIndent) && buffer.isElectricKey('\n', caretLine)) { buffer.indentLine(caretLine, true); } try { buffer.beginCompoundEdit(); setSelectedText("\n"); if ("full".equals(autoIndent)) { if (!buffer.indentLine(caretLine, true)) { // If the line was already correctly indented, the // caret needs to be moved explicitly. if (lineContainsSpaceAndTabs(caretLine)) { goToEndOfLine(false); } else { goToStartOfWhiteSpace(false); } } } else if ("simple".equals(autoIndent)) buffer.simpleIndentLine(caretLine); } finally { buffer.endCompoundEdit(); } } } //}}} //{{{ insertTabAndIndent() method public void insertTabAndIndent() { if(!isEditable()) { getToolkit().beep(); return; } boolean indent = "full".equals(buffer.getStringProperty("autoIndent")); if(indent && getSelectionCount() == 0) { // if caret is inside leading whitespace, indent. CharSequence text = buffer.getLineSegment(caretLine); int start = buffer.getLineStartOffset(caretLine); int whiteSpace = StandardUtilities.getLeadingWhiteSpace(text); if(caret - start <= whiteSpace && buffer.indentLine(caretLine,false)) return; } userInput('\t'); } //}}} //{{{ indentSelectedLines() method /** * Indents all selected lines. * @since jEdit 3.1pre3 */ public void indentSelectedLines() { if(!buffer.isEditable()) getToolkit().beep(); else { buffer.indentLines(getSelectedLines()); selectNone(); } } //}}} //{{{ turnOnElasticTabstops() method /** * Turn ON elastic tab stops. */ public void turnOnElasticTabstops() { if(buffer.isLoading()) return; buffer.indentUsingElasticTabstops(); buffer.elasticTabstopsOn = true; } //}}} //{{{ shiftIndentLeft() method /** * Shifts the indent to the left. * @since jEdit 2.7pre2 */ public void shiftIndentLeft() { if(!buffer.isEditable()) getToolkit().beep(); else { buffer.shiftIndentLeft(getSelectedLines()); } } //}}} //{{{ shiftIndentRight() method /** * Shifts the indent to the right. * @since jEdit 2.7pre2 */ public void shiftIndentRight() { if(!buffer.isEditable()) getToolkit().beep(); else buffer.shiftIndentRight(getSelectedLines()); } //}}} //{{{ joinLines() method /** * Joins the current and the next line, or joins all lines in * selections. * @since jEdit 2.7pre2 */ public void joinLines() { if(!buffer.isEditable()) { getToolkit().beep(); return; } try { buffer.beginCompoundEdit(); boolean doneForSelection = false; for (Selection selection: selectionManager.getSelection()) { while (selection.startLine < selection.endLine) { // Edit from end of selection to // minimize invalidations and // recaluculations of cached line info // such as indent level or fold level. joinLineAt(selection.endLine - 1); doneForSelection = true; } } // If nothing selected or all selections span only // one line, join the line at the caret. if (!doneForSelection) { int end = getLineEndOffset(caretLine); // Nothing to do if the caret is on the last line. if (end > buffer.getLength()) { getToolkit().beep(); return; } joinLineAt(caretLine); if(!multi) selectNone(); moveCaretPosition(end - 1); } } finally { buffer.endCompoundEdit(); } } //}}} //}}} //{{{ AWT stuff //{{{ addLeftOfScrollBar() method /** * Adds a component to the left side of the box left of the vertical * scroll bar. The ErrorList plugin uses this to show a global error * overview, for example. It is possible for more than one component * to be added, each is added to the left side of the box in turn. * Adding to the left ensures the scrollbar is always right of all added * components. * * @param comp The component * @since jEdit 4.2pre1 */ public void addLeftOfScrollBar(Component comp) { verticalBox.add(comp, 0); } //}}} //{{{ removeLeftOfScrollBar() method /** * Removes a component from the box left of the vertical scroll bar. * * @param comp The component * @since jEdit 4.2pre1 */ public void removeLeftOfScrollBar(Component comp) { verticalBox.remove(comp); } //}}} //{{{ addNotify() method /** * Called by the AWT when this component is added to a parent. * Adds document listener. */ @Override public void addNotify() { super.addNotify(); ToolTipManager.sharedInstance().registerComponent(painter); ToolTipManager.sharedInstance().registerComponent(gutter); recalculateVisibleLines(); if(!buffer.isLoading()) recalculateLastPhysicalLine(); propertiesChanged(); } //}}} //{{{ removeNotify() method /** * Called by the AWT when this component is removed from it's parent. * This clears the pointer to the currently focused component. * Also removes document listener. */ @Override public void removeNotify() { super.removeNotify(); ToolTipManager.sharedInstance().unregisterComponent(painter); ToolTipManager.sharedInstance().unregisterComponent(gutter); if(focusedComponent == this) focusedComponent = null; caretTimer.stop(); } //}}} //{{{ getFocusTraversalKeysEnabled() method /** * Java 1.4 compatibility fix to make Tab key work. * @since jEdit 3.2pre4 */ @Override public boolean getFocusTraversalKeysEnabled() { return false; } //}}} //{{{ getFocusCycleRoot() method /** * Java 1.4 compatibility fix to make Tab traversal work in a sane * manner. * @since jEdit 4.2pre3 */ public boolean getFocusCycleRoot() { return true; } //}}} //{{{ processKeyEvent() method @Override public void processKeyEvent(KeyEvent evt) { getInputHandler().processKeyEvent(evt, 1 /* source=TEXTAREA (1) */, false); if(!evt.isConsumed()) super.processKeyEvent(evt); } //}}} //{{{ addTopComponent() method /** * Adds a component above the gutter, text area, and vertical scroll bar. * * @since jEdit 4.2pre3 */ public void addTopComponent(Component comp) { add(ScrollLayout.TOP,comp); } //}}} //{{{ removeTopComponent() method /** * Removes a component from above the gutter, text area, and vertical scroll bar. * * @since jEdit 4.2pre3 */ public void removeTopComponent(Component comp) { remove(comp); } //}}} //{{{ addTopLeftComponent() method /** * Adds a component above the gutter. * * @since jEdit 5.2pre1 */ public void addTopLeftComponent(Component comp) { add(ScrollLayout.TOP_LEFT, comp); } //}}} //{{{ addTopRightComponent() method /** * Adds a component above the vertical scroll bar. * * @since jEdit 5.2pre1 */ public void addTopRightComponent(Component comp) { add(ScrollLayout.TOP_RIGHT, comp); } //}}} //{{{ addBottomLeftComponent() method /** * Adds a component below the gutter. * * @since jEdit 5.2pre1 */ public void addBottomLeftComponent(Component comp) { add(ScrollLayout.BOTTOM_LEFT, comp); } //}}} //{{{ addBottomLeftComponent() method /** * Adds a component below the vertical scroll bar. * * @since jEdit 5.2pre1 */ public void addBottomRightComponent(Component comp) { add(ScrollLayout.BOTTOM_RIGHT, comp); } //}}} //{{{ getInputMethodRequests() method @Override public InputMethodRequests getInputMethodRequests() { if(inputMethodSupport == null) { inputMethodSupport = new InputMethodSupport(this); Log.log(Log.DEBUG, this, "InputMethodSupport is activated"); } return inputMethodSupport; } //}}} //}}} //{{{ addStatusListener() method /** * Adds a scroll listener to this text area. * @param listener The listener * @since jEdit 4.3pre2 */ public final void addStatusListener(StatusListener listener) { listenerList.add(StatusListener.class,listener); } //}}} //{{{ removeStatusListener() method /** * Removes a scroll listener from this text area. * @param listener The listener * @since jEdit 4.3pre2 */ public final void removeStatusListener(StatusListener listener) { listenerList.remove(StatusListener.class,listener); } //}}} //{{{ propertiesChanged() method /** * Called by jEdit when necessary. Plugins should not call this method. */ public void propertiesChanged() { if(buffer == null) return; if(buffer.getBooleanProperty("elasticTabstops")) { //call this only if it was previously off if(!buffer.elasticTabstopsOn) { turnOnElasticTabstops(); } if(buffer.getColumnBlock()!=null) { buffer.getColumnBlock().setTabSizeDirtyStatus(true, true); } } else { buffer.elasticTabstopsOn = false; } int _tabSize = buffer.getTabSize(); char[] foo = new char[_tabSize]; for(int i = 0; i < foo.length; i++) foo[i] = ' '; tabSize = painter.getStringWidth(new String(foo)); // Calculate an average to use a reasonable value for // propotional fonts. String charWidthSample = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; charWidth = (int)Math.round( painter.getFont().getStringBounds(charWidthSample, painter.getFontRenderContext()).getWidth() / charWidthSample.length()); String oldWrap = wrap; wrap = buffer.getStringProperty("wrap"); hardWrap = "hard".equals(wrap); String largeFileMode = buffer.getStringProperty("largefilemode"); softWrap = "soft".equals(wrap) && !"limited".equals(largeFileMode) && !"nohighlight".equals(largeFileMode); boolean oldWrapToWidth = wrapToWidth; int oldWrapMargin = wrapMargin; setMaxLineLength(buffer.getIntegerProperty("maxLineLen",0)); boolean wrapSettingsChanged = !(wrap.equals(oldWrap) && oldWrapToWidth == wrapToWidth && oldWrapMargin == wrapMargin); if(displayManager != null && !bufferChanging && !buffer.isLoading() && wrapSettingsChanged) { displayManager.invalidateScreenLineCounts(); displayManager.notifyScreenLineChanges(); } chunkCache.reset(); gutter.repaint(); painter.repaint(); } //}}} //{{{ addActionSet() method /** * Adds a new action set to the textarea's list of ActionSets. * Call this only on standalone textarea * * @param actionSet the actionSet to add * @since jEdit 4.3pre13 */ public void addActionSet(JEditActionSet<JEditBeanShellAction> actionSet) { actionContext.addActionSet(actionSet); } //}}} //{{{ getMarkPosition() method /** * @deprecated Do not use. */ @Deprecated public final int getMarkPosition() { Selection s = getSelectionAtOffset(caret); if(s == null) return caret; if(s.start == caret) return s.end; else if(s.end == caret) return s.start; else return caret; } //}}} //{{{ Package-private members static TextArea focusedComponent; //{{{ Instance variables MouseInputAdapter mouseHandler; final ChunkCache chunkCache; DisplayManager displayManager; final SelectionManager selectionManager; /** * The action context. * It is used only when the textarea is standalone */ private JEditActionContext<JEditBeanShellAction,JEditActionSet<JEditBeanShellAction>> actionContext; boolean bufferChanging; int maxHorizontalScrollWidth; String wrap; boolean hardWrap; boolean softWrap; boolean wrapToWidth; int maxLineLen; int wrapMargin; float tabSize; int charWidth; boolean scrollBarsInitialized; /** * Cursor location, measured as an offset (in pixels) from upper left corner * of the TextArea. */ final Point offsetXY; boolean lastLinePartial; boolean blink; //}}} //{{{ isCaretVisible() method /** * Returns true if the caret is visible, false otherwise. */ public final boolean isCaretVisible() { return blink && hasFocus(); } //}}} //{{{ isStructureHighlightVisible() method /** * Returns true if the structure highlight is visible, false otherwise. * @since jEdit 4.2pre3 */ final boolean isStructureHighlightVisible() { return match != null && hasFocus() && displayManager.isLineVisible(match.startLine) && displayManager.isLineVisible(match.endLine); } //}}} //{{{ updateMaxHorizontalScrollWidth() method void updateMaxHorizontalScrollWidth() { int max = chunkCache.getMaxHorizontalScrollWidth(); if(max != maxHorizontalScrollWidth) { maxHorizontalScrollWidth = max; horizontal.setValues(Math.max(0, Math.min(maxHorizontalScrollWidth + charWidth - painter.getWidth(), -horizontalOffset)), painter.getWidth(), 0,maxHorizontalScrollWidth + charWidth); horizontal.setUnitIncrement(10); horizontal.setBlockIncrement(painter.getWidth()); } else if (horizontal.getValue() != -horizontalOffset) { horizontal.setValue(-horizontalOffset); } } //}}} //{{{ recalculateVisibleLines() method void recalculateVisibleLines() { if(painter == null) return; int height = painter.getHeight(); int lineHeight = painter.getLineHeight(); if(lineHeight == 0) visibleLines = 0; else if(height <= 0) { visibleLines = 0; lastLinePartial = false; } else { visibleLines = height / lineHeight; lastLinePartial = height % lineHeight != 0; if(lastLinePartial) visibleLines++; } chunkCache.recalculateVisibleLines(); // this does the "trick" to eliminate blank space at the end if(displayManager != null && buffer != null && !buffer.isLoading()) setFirstLine(getFirstLine()); updateScrollBar(); } //}}} //{{{ foldStructureChanged() method void foldStructureChanged() { chunkCache.invalidateAll(); recalculateLastPhysicalLine(); if(!displayManager.isLineVisible(caretLine)) { int x = chunkCache.subregionOffsetToX(caretLine, caret - getLineStartOffset(caretLine)); int line = displayManager.getPrevVisibleLine(caretLine); if(!multi) { // cannot use selectNone() because the finishCaretUpdate method will reopen the fold invalidateSelectedLines(); selectionManager.setSelection((Selection) null); } moveCaretPosition(buffer.getLineStartOffset(line) + chunkCache.xToSubregionOffset(line,0,x,true)); } repaint(); } //}}} //{{{ updateScrollBar() method /** * Updates the state of the scroll bars. This should be called * if the number of lines in the buffer changes, or when the * size of the text are changes. */ void updateScrollBar() { if(buffer == null) return; if(Debug.SCROLL_DEBUG) Log.log(Log.DEBUG,this,"updateScrollBar(), slc=" + displayManager.getScrollLineCount()); if(vertical != null && visibleLines != 0) { if(Debug.SCROLL_DEBUG) Log.log(Log.DEBUG,this,"Vertical ok"); final int lineCount = displayManager.getScrollLineCount(); final int firstLine = getFirstLine(); final int visible = visibleLines - (lastLinePartial ? 1 : 0); Runnable runnable = new Runnable() { @Override public void run() { vertical.setValues(firstLine,visible,0,lineCount); vertical.setUnitIncrement(2); vertical.setBlockIncrement(visible); } }; ThreadUtilities.runInDispatchThread(runnable); } } //}}} //{{{ _finishCaretUpdate() method /* called by DisplayManager.BufferChangeHandler.transactionComplete() */ void _finishCaretUpdate() { if(!queuedCaretUpdate) return; try { if(match != null) { if(oldCaretLine < match.startLine) invalidateLineRange(oldCaretLine,match.endLine); else invalidateLineRange(match.startLine,oldCaretLine); match = null; } int newCaretScreenLine = chunkCache.getScreenLineOfOffset(caretLine, caret - buffer.getLineStartOffset(caretLine)); if(caretScreenLine == -1) invalidateScreenLineRange(newCaretScreenLine,newCaretScreenLine); else invalidateScreenLineRange(caretScreenLine,newCaretScreenLine); caretScreenLine = newCaretScreenLine; invalidateSelectedLines(); // When the user is typing, etc, we don't want the caret // to blink blink = true; caretTimer.restart(); if(!displayManager.isLineVisible(caretLine)) { // If we've jumped outside of a narrowed display, just reset all // folds to their default level, so that we don't get disconnected // islands of visible lines. if(displayManager.isOutsideNarrowing(caretLine)) { int collapseFolds = buffer.getIntegerProperty( "collapseFolds",0); if(collapseFolds != 0) { displayManager.expandFolds(collapseFolds, false); displayManager.expandFold(caretLine, false); foldStructureChanged(); } else displayManager.expandAllFolds(); } else displayManager.expandFold(caretLine,false); } if(queuedScrollMode == ELECTRIC_SCROLL) scrollToCaret(true); else if(queuedScrollMode == NORMAL_SCROLL) scrollToCaret(false); updateBracketHighlightWithDelay(); if(queuedFireCaretEvent) fireCaretEvent(); } // in case one of the above fails, we still want to // clear these flags. finally { queuedCaretUpdate = queuedFireCaretEvent = false; queuedScrollMode = NO_SCROLL; } } //}}} //{{{ invalidateStructureMatch() method void invalidateStructureMatch() { if(match != null) invalidateLineRange(match.startLine,match.endLine); } //}}} //{{{ startDragAndDrop() method void startDragAndDrop(InputEvent evt, boolean copy) { TransferHandler transferHandler = getTransferHandler(); if (transferHandler != null) { Log.log(Log.DEBUG,this,"Drag and drop callback"); transferHandler.exportAsDrag(this,evt, copy ? TransferHandler.COPY : TransferHandler.MOVE); } } //}}} //{{{ fireNarrowActive() method void fireNarrowActive() { Object[] listeners = listenerList.getListenerList(); for(int i = listeners.length - 2; i >= 0; i--) { if(listeners[i] == StatusListener.class) { try { ((StatusListener)listeners[i+1]) .narrowActive(this); } catch(Throwable t) { Log.log(Log.ERROR,this,t); } } } } //}}} //}}} //{{{ Private members //{{{ Static variables private static final Timer caretTimer; private static final Timer structureTimer; //}}} //{{{ Instance variables protected JPopupMenu popup; private boolean popupEnabled; private final Gutter gutter; protected final TextAreaPainter painter; private final EventListenerList listenerList; private final MutableCaretEvent caretEvent; private boolean caretBlinks; private final ElasticTabstopsTabExpander elasticTabstopsExpander = new ElasticTabstopsTabExpander(this); protected InputHandlerProvider inputHandlerProvider; private InputMethodSupport inputMethodSupport; /** The last visible physical line index. */ private int physLastLine; /** * The last screen line index. */ private int screenLastLine; /** The visible lines count. */ private int visibleLines; private int electricScroll; private int horizontalOffset; private boolean quickCopy; // JDiff, error list add stuff here private final Box verticalBox; private final JScrollBar vertical; private final JScrollBar horizontal; protected JEditBuffer buffer; protected int caret; protected int caretLine; private int caretScreenLine; private final java.util.List<StructureMatcher> structureMatchers; private StructureMatcher.Match match; private int magicCaret; /** Flag that tells if multiple selection is on. */ protected boolean multi; private boolean overwrite; private boolean rectangularSelectionMode; private boolean dndEnabled; // see finishCaretUpdate() & _finishCaretUpdate() private boolean queuedCaretUpdate; private int queuedScrollMode; private boolean queuedFireCaretEvent; private int oldCaretLine; private boolean joinNonWordChars; private boolean ctrlForRectangularSelection; //}}} //{{{ _setHorizontalOffset() method /** * Sets the horizontal offset of drawn lines. This method will * check if the offset do not go too far after the last character * @param horizontalOffset offset The new horizontal offset */ private void _setHorizontalOffset(int horizontalOffset) { if(horizontalOffset > 0) horizontalOffset = 0; if(horizontalOffset == this.horizontalOffset) return; // Scrolling with trackpad or other device should be kept inside bounds int min = Math.min(-(maxHorizontalScrollWidth + charWidth - painter.getWidth()), 0); if(horizontalOffset < min) horizontalOffset = min; setHorizontalOffset(horizontalOffset); } //}}} //{{{ invalidateSelectedLines() method /** * Repaints the lines containing the selection. */ private void invalidateSelectedLines() { // to hide line highlight if selections are being added later on invalidateLine(caretLine); for (Selection s : selectionManager.selection) invalidateLineRange(s.startLine,s.endLine); } //}}} //{{{ finishCaretUpdate() method /** * the collapsing of scrolling/event firing inside compound edits * greatly speeds up replace-all. */ private void finishCaretUpdate(int oldCaretLine, int scrollMode, boolean fireCaretEvent) { queuedFireCaretEvent |= fireCaretEvent; queuedScrollMode = Math.max(scrollMode,queuedScrollMode); if(queuedCaretUpdate) return; this.oldCaretLine = oldCaretLine; queuedCaretUpdate = true; if(!buffer.isTransactionInProgress()) _finishCaretUpdate(); /* otherwise DisplayManager.BufferChangeHandler calls */ } //}}} //{{{ fireCaretEvent() method private void fireCaretEvent() { Object[] listeners = listenerList.getListenerList(); for(int i = listeners.length - 2; i >= 0; i--) { if(listeners[i] == CaretListener.class) { try { ((CaretListener)listeners[i+1]).caretUpdate(caretEvent); } catch(Throwable t) { Log.log(Log.ERROR,this,t); } } } } //}}} //{{{ fireScrollEvent() method private void fireScrollEvent(boolean vertical) { Object[] listeners = listenerList.getListenerList(); for(int i = listeners.length - 2; i >= 0; i--) { if(listeners[i] == ScrollListener.class) { try { if(vertical) ((ScrollListener)listeners[i+1]).scrolledVertically(this); else ((ScrollListener)listeners[i+1]).scrolledHorizontally(this); } catch(Throwable t) { Log.log(Log.ERROR,this,t); } } } } //}}} //{{{ fireStatusChanged() method private void fireStatusChanged(int flag, boolean value) { Object[] listeners = listenerList.getListenerList(); for(int i = listeners.length - 2; i >= 0; i--) { if(listeners[i] == StatusListener.class) { try { ((StatusListener)listeners[i+1]) .statusChanged(this,flag,value); } catch(Throwable t) { Log.log(Log.ERROR,this,t); } } } } //}}} //{{{ fireBracketSelected() method private void fireBracketSelected(int line, String text) { Object[] listeners = listenerList.getListenerList(); for(int i = listeners.length - 2; i >= 0; i--) { if(listeners[i] == StatusListener.class) { try { ((StatusListener)listeners[i+1]) .bracketSelected(this,line,text); } catch(Throwable t) { Log.log(Log.ERROR,this,t); } } } } //}}} //{{{ _changeLine() method private void _changeLine(boolean select, int newCaret) { if(select) { RectParams params = getRectParams(caret,newCaret); int extraStartVirt; int extraEndVirt; if(params == null) { extraStartVirt = 0; extraEndVirt = 0; } else { extraStartVirt = params.extraStartVirt; extraEndVirt = params.extraEndVirt; newCaret = params.newCaret; } extendSelection(caret,newCaret,extraStartVirt,extraEndVirt); } else if(!multi) selectNone(); moveCaretPosition(newCaret); }//}}} //{{{ lineContainsSpaceAndTabs() method /** * Check if the line contains only spaces and tabs. * * @param lineIndex the line index * @return <code>true</code> if the line contains only spaces and tabs */ private boolean lineContainsSpaceAndTabs(int lineIndex) { final Segment lineSegment = new Segment(); getLineText(lineIndex,lineSegment); for(int j = 0; j < lineSegment.count; j++) { switch(lineSegment.array[lineSegment.offset + j]) { case ' ': case '\t': break; default: return false; } } return true; } //}}} //{{{ insert() method protected void insert(String str, boolean indent) { try { // Don't overstrike if we're on the end of // the line if(overwrite || indent) buffer.beginCompoundEdit(); if(overwrite) { int caretLineEnd = getLineEndOffset(caretLine); if(caretLineEnd - caret > 1) deleteNextCharacter(caret); } buffer.insert(caret,str); if(indent) buffer.indentLine(caretLine,true); } finally { if(overwrite || indent) buffer.endCompoundEdit(); } } //}}} //{{{ insertTab() method private void insertTab() { int tabSize = buffer.getTabSize(); if(buffer.getBooleanProperty("noTabs")) { int lineStart = getLineStartOffset(caretLine); String line = getText(lineStart,caret - lineStart); int pos = 0; for(int i = 0; i < line.length(); i++) { switch(line.charAt(pos)) { case '\t': pos = 0; break; default: if(++pos >= tabSize) pos = 0; break; } } replaceSelection(StandardUtilities.createWhiteSpace( tabSize - pos,0)); } else replaceSelection("\t"); } //}}} //{{{ userInputTab() method protected void userInputTab() { if(getSelectionCount() == 1) { Selection sel = getSelection(0); if(sel instanceof Selection.Rect || (sel.startLine == sel.endLine && (sel.start != buffer.getLineStartOffset(sel.startLine) || sel.end != buffer.getLineEndOffset(sel.startLine) - 1))) { insertTab(); } else shiftIndentRight(); } else if(getSelectionCount() != 0) shiftIndentRight(); else insertTab(); } //}}} //{{{ doWordWrap() method /** * Does hard wrap. */ protected boolean doWordWrap(boolean spaceInserted) { if(!hardWrap || maxLineLen <= 0) return false; final Segment lineSegment = new Segment(); buffer.getLineText(caretLine,lineSegment); int start = getLineStartOffset(caretLine); int end = getLineEndOffset(caretLine); int len = end - start - 1; int caretPos = caret - start; // only wrap if we're at the end of a line, or the rest of the // line text is whitespace for(int i = caretPos; i < len; i++) { char ch = lineSegment.array[lineSegment.offset + i]; if(ch != ' ' && ch != '\t') return false; } int tabSize = buffer.getTabSize(); String wordBreakChars = buffer.getStringProperty("wordBreakChars"); int lastInLine = 0; // last character before wrap int logicalLength = 0; // length with tabs expanded int lastWordOffset = -1; boolean lastWasSpace = true; for(int i = 0; i < caretPos; i++) { char ch = lineSegment.array[lineSegment.offset + i]; if(ch == '\t') { logicalLength += tabSize - (logicalLength % tabSize); if(!lastWasSpace && logicalLength <= maxLineLen) { lastInLine = i; lastWordOffset = i; lastWasSpace = true; } } else if(ch == ' ') { logicalLength++; if(!lastWasSpace && logicalLength <= maxLineLen + 1) { lastInLine = i; lastWordOffset = i; lastWasSpace = true; } } else if(wordBreakChars != null && wordBreakChars.indexOf(ch) != -1) { logicalLength++; if(!lastWasSpace && logicalLength <= maxLineLen) { lastInLine = i; lastWordOffset = i; lastWasSpace = true; } } else { lastInLine = i; logicalLength++; lastWasSpace = false; } } boolean returnValue; int insertNewLineAt; if(spaceInserted && logicalLength == maxLineLen && lastInLine == caretPos - 1) { insertNewLineAt = caretPos; returnValue = true; } else if(logicalLength >= maxLineLen && lastWordOffset != -1) { insertNewLineAt = lastWordOffset; returnValue = false; } else return false; String indent = buffer.getStringProperty("autoIndent"); try { buffer.beginCompoundEdit(); buffer.insert(start + insertNewLineAt,"\n"); // caretLine would have been incremented // since insertNewLineAt <= caretPos if ("full".equals(indent)) buffer.indentLine(caretLine,true); else if ("simple".equals(indent)) buffer.simpleIndentLine(caretLine); } finally { buffer.endCompoundEdit(); } /* only ever return true if space was pressed * with logicalLength == maxLineLen */ return returnValue; } //}}} //{{{ updateStructureHighlightWithDelay() method private static void updateBracketHighlightWithDelay() { structureTimer.stop(); structureTimer.start(); } //}}} //{{{ updateStructureHighlight() method private void updateStructureHighlight() { if(!painter.isStructureHighlightEnabled() && !gutter.isStructureHighlightEnabled()) return; for (StructureMatcher matcher : structureMatchers) { match = matcher.getMatch(this); if(match != null) break; } if(match != null) { if(caretLine < match.startLine) invalidateLineRange(caretLine,match.endLine); else invalidateLineRange(match.startLine,caretLine); if(!displayManager.isLineVisible(match.startLine) || chunkCache.getScreenLineOfOffset( match.startLine,match.start - getLineStartOffset(match.startLine)) == -1) { showStructureStatusMessage(match.startLine < caretLine); } } } //}}} //{{{ showStructureStatusMessage() method private void showStructureStatusMessage(boolean backward) { String text = buffer.getLineText(match.startLine).trim(); if(backward && match.startLine != 0 && text.length() == 1) { switch(text.charAt(0)) { case '{': case '}': case '[': case ']': case '(': case ')': text = buffer.getLineText(match.startLine - 1) .trim() + ' ' + text; break; } } // get rid of embedded tabs not removed by trim() fireBracketSelected(match.startLine + 1,text.replace('\t',' ')); } //}}} //{{{ recalculateLastPhysicalLine() method void recalculateLastPhysicalLine() { int oldScreenLastLine = screenLastLine; for(int i = visibleLines - 1; i >= 0; i--) { ChunkCache.LineInfo info = chunkCache.getLineInfo(i); if(info.physicalLine != -1) { physLastLine = info.physicalLine; screenLastLine = i; break; } } invalidateScreenLineRange(oldScreenLastLine,screenLastLine); } //}}} //{{{ getRectParams() method private static class RectParams { final int extraStartVirt; final int extraEndVirt; final int newCaret; RectParams(int extraStartVirt, int extraEndVirt, int newCaret) { this.extraStartVirt = extraStartVirt; this.extraEndVirt = extraEndVirt; this.newCaret = newCaret; } } /** * Used when doing S+UP/DOWN to simplify dealing with virtual space. */ private RectParams getRectParams(int caret, int newCaret) { Selection s = getSelectionAtOffset(caret); int virtualWidth; if(s instanceof Selection.Rect) { if(caret == s.end) { virtualWidth = buffer.getVirtualWidth( s.endLine,s.end - getLineStartOffset( s.endLine)) + ((Selection.Rect)s).extraEndVirt; } else { virtualWidth = buffer.getVirtualWidth( s.startLine,s.start - getLineStartOffset( s.startLine)) + ((Selection.Rect)s).extraStartVirt; } } else if(rectangularSelectionMode) { virtualWidth = buffer.getVirtualWidth( caretLine,caret - buffer.getLineStartOffset(caretLine)); } else return null; int newLine = getLineOfOffset(newCaret); int[] totalVirtualWidth = new int[1]; int newOffset = buffer.getOffsetOfVirtualColumn(newLine, virtualWidth,totalVirtualWidth); if(newOffset == -1) { int extraVirt = virtualWidth - totalVirtualWidth[0]; newCaret = getLineEndOffset(newLine) - 1; boolean bias; if(s == null) bias = newCaret < caret; else if(s.start == caret) bias = newCaret <= s.end; else if(s.end == caret) bias = newCaret <= s.start; else bias = false; RectParams returnValue; if(bias) returnValue = new RectParams(extraVirt,0,newCaret); else returnValue = new RectParams(0,extraVirt,newCaret); return returnValue; } else { return new RectParams(0,0,getLineStartOffset(newLine) + newOffset); } } //}}} //{{{ deleteNextCharacter() method // Delete a code point or combining character sequence at once. private void deleteNextCharacter(int offset) { assert offset < buffer.getLength(); int length = getNextCharacterOffset(offset) - offset; buffer.remove(offset, length); } //}}} //{{{ deletePrevCodePoint() method // Delete a code point. // This is the behavior of backward removal on Windows Notepad. private void deletePrevCodePoint(int offset) { assert offset > 0; int length = 1; if (offset >= 2) { Segment prevText = new Segment(); buffer.getText(offset - 1, 1, prevText); char prevCodeUnit = prevText.array[prevText.offset]; if (Character.isLowSurrogate(prevCodeUnit)) { buffer.getText(offset - 2, 1, prevText); prevCodeUnit = prevText.array[prevText.offset]; if (Character.isHighSurrogate(prevCodeUnit)) { length = 2; } } } buffer.remove(offset - length, length); } //}}} //{{{ delete() method private void delete(boolean forward) { if(!buffer.isEditable()) { getToolkit().beep(); return; } if(getSelectionCount() != 0) { Selection[] selections = getSelection(); for (Selection s : selections) { if(s instanceof Selection.Rect) { Selection.Rect r = (Selection.Rect)s; int startColumn = r.getStartColumn(buffer); if(startColumn == r.getEndColumn(buffer)) { if(!forward && startColumn == 0) getToolkit().beep(); else tallCaretDelete(r,forward); } else setSelectedText(s,null); } else setSelectedText(s,null); } } else if(forward) { if(caret == buffer.getLength()) { getToolkit().beep(); return; } deleteNextCharacter(caret); } else { if(caret == 0) { getToolkit().beep(); return; } deletePrevCodePoint(caret); } } //}}} //{{{ tallCaretDelete() method private void tallCaretDelete(Selection.Rect s, boolean forward) { try { buffer.beginCompoundEdit(); int[] width = new int[1]; int startCol = s.getStartColumn(buffer); int startLine = s.startLine; int endLine = s.endLine; for(int i = startLine; i <= endLine; i++) { int offset = buffer.getOffsetOfVirtualColumn( i,startCol,width); if(offset == -1) { if(width[0] == startCol) offset = getLineLength(i); else { if(i == startLine && !forward) shiftTallCaretLeft(s); continue; } } offset += buffer.getLineStartOffset(i); if(forward) { if(offset != buffer.getLineEndOffset(i) - 1) deleteNextCharacter(offset); } else deletePrevCodePoint(offset); } } finally { buffer.endCompoundEdit(); } } //}}} //{{{ shiftTallCaretLeft() method private void shiftTallCaretLeft(Selection.Rect s) { removeFromSelection(s); addToSelection(new Selection.Rect( buffer, s.getStartLine(),s.getStartColumn(buffer) - 1, s.getEndLine(),s.getEndColumn(buffer) - 1)); } //}}} //{{{ setMaxLineLength() method private void setMaxLineLength(int maxLineLen) { this.maxLineLen = maxLineLen; if(maxLineLen <= 0) { if(softWrap) { wrapToWidth = true; wrapMargin = painter.getWidth() - charWidth * 3; } else { wrapToWidth = false; wrapMargin = 0; } } else { int estimate = charWidth * maxLineLen; if (softWrap && painter.getWidth() < estimate) { wrapToWidth = true; wrapMargin = painter.getWidth() - charWidth * 3; } else { wrapToWidth = false; wrapMargin = estimate; } } } //}}} //{{{ addExplicitFold() method /** * Add an explicit fold. * You should call this method inside a compoundEdit in the buffer. * You must also check if the buffer fold mode is explicit before * calling this method. * * @param caretStart the starting offset * @param caretEnd the end offset * @param lineStart the start line * @param lineEnd the end line * @since jEdit 4.3pre3 */ protected int addExplicitFold(int caretStart, int caretEnd, int lineStart, int lineEnd) { // need to "fix" the caret position so that we get the right rule. // taking the start offset one char ahead and the end offset one char // behing makes sure we get the right rule for the text being // wrapped (tricky around mode boundaries, e.g., php code embedded // in HTML code) int startCaret = caretStart < buffer.getLength() ? caretStart + 1 : caretStart; int endCaret = caretEnd > 0 ? caretEnd - 1 : caretEnd; String startLineComment = buffer.getContextSensitiveProperty(startCaret,"lineComment"); String startCommentStart = buffer.getContextSensitiveProperty(startCaret,"commentStart"); String startCommentEnd = buffer.getContextSensitiveProperty(startCaret,"commentEnd"); String endLineComment = buffer.getContextSensitiveProperty(endCaret,"lineComment"); String endCommentStart = buffer.getContextSensitiveProperty(endCaret,"commentStart"); String endCommentEnd = buffer.getContextSensitiveProperty(endCaret,"commentEnd"); String start; int caretBack = 1; if(startLineComment != null) start = startLineComment + "{{{ "; else if(startCommentStart != null && startCommentEnd != null) { start = startCommentStart + "{{{ " + startCommentEnd; caretBack = 2 + startCommentEnd.length(); } else start = "{{{ "; if (startLineComment != null) { // add a new line if there's text after the comment // we're inserting if (buffer.getLineLength(lineStart) != caretStart) { start += '\n'; } } else { // always insert a new line if there's no comment character. start += "\n"; } String end; if(endLineComment != null) end = endLineComment + "}}}"; else if(endCommentStart != null && endCommentEnd != null) end = endCommentStart + "}}}" + endCommentEnd; else end = "}}}"; String line = buffer.getLineText(lineStart); String whitespace = line.substring(0, StandardUtilities.getLeadingWhiteSpace(line)); caretBack += whitespace.length(); if (caretStart == caretEnd) { caretBack += end.length() + 1; int lineStartOffset = buffer.getLineStartOffset(lineStart); if (lineStartOffset + whitespace.length() != caretStart) { caretBack++; } } if (endLineComment != null) { // if we're inserting a line comment into a non-empty // line, we'll need to add a line break so we don't // comment out existing code. if (buffer.getLineLength(lineEnd) != caretEnd) { end += '\n'; } } else { // always insert a new line if there's no comment character. end += "\n"; } if(caretEnd == buffer.getLineStartOffset(lineEnd)) buffer.insert(caretEnd,end); else { CharSequence lineText = buffer.getSegment(caretEnd - 1, 1); if (Character.isWhitespace(lineText.charAt(0))) buffer.insert(caretEnd, end); else buffer.insert(caretEnd,' ' + end); } buffer.insert(caretStart,start + whitespace); return caretBack; } //}}} //{{{ rangeLineComment() method /** * This method will surround each selected line with a range comment. * This is used when calling line comment if the edit mode doesn't have * a line comment property * @since jEdit 4.3pre10 */ private void rangeLineComment() { String commentStart = buffer.getContextSensitiveProperty(caret,"commentStart"); String commentEnd = buffer.getContextSensitiveProperty(caret,"commentEnd"); if(!buffer.isEditable() || commentStart == null || commentEnd == null || commentStart.length() == 0 || commentEnd.length() == 0) { getToolkit().beep(); return; } commentStart += ' '; commentEnd = ' ' + commentEnd; try { buffer.beginCompoundEdit(); int[] lines = getSelectedLines(); for (int line : lines) { String text = getLineText(line); if (text.trim().length() == 0) continue; buffer.insert(getLineEndOffset(line) - 1, commentEnd); buffer.insert(getLineStartOffset(line) + StandardUtilities.getLeadingWhiteSpace(text), commentStart); } } finally { buffer.endCompoundEdit(); } } //}}} //{{{ joinLine() method /** * Join a line with the next line. * If you use this method you have to lock the buffer in compound edit mode. * @param line the line number that will be joined with the next line */ private void joinLineAt(int line) { if (line >= buffer.getLineCount() - 1) return; int end = getLineEndOffset(line); CharSequence nextLineText = buffer.getLineSegment(line + 1); buffer.remove(end - 1,StandardUtilities.getLeadingWhiteSpace( nextLineText) + 1); if (nextLineText.length() != 0) buffer.insert(end - 1, " "); } //}}} //}}} //{{{ isRightClickPopupEnabled() method /** * Returns if the right click popup menu is enabled. The Gestures * plugin uses this API. * @since jEdit 4.2pre13 */ public boolean isRightClickPopupEnabled() { return popupEnabled; } //}}} //{{{ setRightClickPopupEnabled() method /** * Sets if the right click popup menu is enabled. The Gestures * plugin uses this API. * @since jEdit 4.2pre13 */ public void setRightClickPopupEnabled(boolean popupEnabled) { this.popupEnabled = popupEnabled; } //}}} //{{{ getRightClickPopup() method /** * Returns the right click popup menu. */ public final JPopupMenu getRightClickPopup() { return popup; } //}}} //{{{ setRightClickPopup() method /** * Sets the right click popup menu. * @param popup The popup */ public final void setRightClickPopup(JPopupMenu popup) { this.popup = popup; } //}}} //{{{ handlePopupTrigger() method /** * Do the same thing as right-clicking on the text area. The Gestures * plugin uses this API. * @since jEdit 4.2pre13 */ public void handlePopupTrigger(MouseEvent evt) { // Rebuild popup menu every time the menu is requested. createPopupMenu(evt); int x = evt.getX(); int y = evt.getY(); int dragStart = xyToOffset(x,y, !(painter.isBlockCaretEnabled() || isOverwriteEnabled())); if(getSelectionCount() == 0 || multi) moveCaretPosition(dragStart,false); showPopupMenu(popup,this,x,y,false); } //}}} //{{{ createPopupMenu() method /** * Creates the popup menu. * If you want a popup menu, don't forget in your class to * call {@link #setRightClickPopupEnabled(boolean)} to enable the * popup menu * @since 4.3pre15 */ public void createPopupMenu(MouseEvent evt) { } //}}} //{{{ showPopupMenu() method /** * Shows the popup menu below the current caret position. * @since 4.3pre10 */ public void showPopupMenu() { if (!popup.isVisible() && hasFocus()) { Point caretPos = offsetToXY(getCaretPosition()); if (caretPos != null) { // Open the context menu below the caret int charHeight = getPainter().getLineHeight(); showPopupMenu(popup, painter,caretPos.x,caretPos.y + charHeight,true); } } } //}}} //{{{ showPopupMenu() method - copied from GUIUtilities /** * Shows the specified popup menu, ensuring it is displayed within * the bounds of the screen. * @param popup The popup menu * @param comp The component to show it for * @param x The x co-ordinate * @param y The y co-ordinate * @param point If true, then the popup originates from a single point; * otherwise it will originate from the component itself. This affects * positioning in the case where the popup does not fit onscreen. * * @since jEdit 4.1pre1 */ public static void showPopupMenu(JPopupMenu popup, Component comp, int x, int y, boolean point) { int offsetX = 0; int offsetY = 0; int extraOffset = point ? 1 : 0; Component win = comp; while(!(win instanceof Window || win == null)) { offsetX += win.getX(); offsetY += win.getY(); win = win.getParent(); } if(win != null) { Dimension size = popup.getPreferredSize(); Rectangle screenSize = GraphicsEnvironment .getLocalGraphicsEnvironment().getMaximumWindowBounds(); if(x + offsetX + size.width + win.getX() > screenSize.width && x + offsetX + win.getX() >= size.width) { //System.err.println("x overflow"); if(point) x -= size.width + extraOffset; else x = win.getWidth() - size.width - offsetX + extraOffset; } else { x += extraOffset; } //System.err.println("y=" + y + ",offsetY=" + offsetY // + ",size.height=" + size.height // + ",win.height=" + win.getHeight()); if(y + offsetY + size.height + win.getY() > screenSize.height && y + offsetY + win.getY() >= size.height) { if(point) y = win.getHeight() - size.height - offsetY + extraOffset; else y = -size.height - 1; } else { y += extraOffset; } popup.show(comp,x,y); } else popup.show(comp,x + extraOffset,y + extraOffset); } //}}} //{{{ Character boundary staffs //{{{ LineCharacterBreaker class // Shared context among some operations which are aware of // characters above BMP and combining character sequence. private static class LineCharacterBreaker { private final BreakIterator charBreaker; private final int index0Offset; public LineCharacterBreaker(TextArea textArea, int offset) { final int line = textArea.getLineOfOffset(offset); charBreaker = BreakIterator.getCharacterInstance(); charBreaker.setText(new CharIterator(textArea.buffer.getLineSegment(line))); index0Offset = textArea.getLineStartOffset(line); } public boolean offsetIsBoundary(int offset) { return charBreaker.isBoundary(offset - index0Offset); } public int nextOf(int offset) { int following = charBreaker.following(offset - index0Offset); if (following == BreakIterator.DONE) { // This means a end of line. Then it is // safe to assume 1 code unit is a character. // This may return an offset beyond the // length of buffer. But it is a callers // responsibility. return offset + 1; } return following + index0Offset; } public int previousOf(int offset) { int preceding = charBreaker.preceding(offset - index0Offset); if (preceding == BreakIterator.DONE) { // This means a start of line. Then it is // safe to assume 1 code unit is a character. // This may return -1. But it is a callers // responsibility. return offset - 1; } return preceding + index0Offset; } //{{{ CharIterator class // This class adapt CharSequence, which is used to avoid // text copy, to CharacterIterator, which is used to pass // a text to BreakIterator. private static class CharIterator implements CharacterIterator { private final CharSequence sequence; private int index; public CharIterator(CharSequence sequence) { this.sequence = sequence; index = 0; } @Override public char first() { index = 0; return current(); } @Override public char last() { int length = sequence.length(); index = (length > 0) ? length - 1 : length; return current(); } @Override public char current() { return index < sequence.length() ? sequence.charAt(index) : DONE; } @Override public char next() { int length = sequence.length(); if (index < length) { index = index + 1; return current(); } else { return DONE; } } @Override public char previous() { if (index > 0) { index = index - 1; return current(); } else { return DONE; } } @Override public char setIndex(int position) { if (0 <= position && position <= sequence.length()) { index = position; return current(); } else { // There should be a bug in caller. // Stacktrace will be enough. throw new IllegalArgumentException(); } } @Override public int getBeginIndex() { return 0; } @Override public int getEndIndex() { return sequence.length(); } @Override public int getIndex() { return index; } @Override public Object clone() { CharIterator newOne = new CharIterator(sequence); newOne.index = index; return newOne; } } //}}} } //}}} private int getPrevCharacterOffset(int offset) { return new LineCharacterBreaker(this, offset).previousOf(offset); } private int getNextCharacterOffset(int offset) { return new LineCharacterBreaker(this, offset).nextOf(offset); } private int getCharacterBoundaryAt(int offset) { final LineCharacterBreaker charBreaker = new LineCharacterBreaker(this, offset); return charBreaker.offsetIsBoundary(offset) ? offset : charBreaker.previousOf(offset); } //}}} //{{{ Inner classes //{{{ CaretBlinker class private static class CaretBlinker implements ActionListener { //{{{ actionPerformed() method @Override public void actionPerformed(ActionEvent evt) { if(focusedComponent != null && focusedComponent.hasFocus()) focusedComponent.blinkCaret(); } //}}} } //}}} //{{{ MutableCaretEvent class private class MutableCaretEvent extends CaretEvent { //{{{ MutableCaretEvent constructor MutableCaretEvent() { super(TextArea.this); } //}}} //{{{ getDot() method @Override public int getDot() { return getCaretPosition(); } //}}} //{{{ getMark() method @Override public int getMark() { return getMarkPosition(); } //}}} } //}}} //{{{ AdjustHandler class private class AdjustHandler implements AdjustmentListener { //{{{ adjustmentValueChanged() method @Override public void adjustmentValueChanged(AdjustmentEvent evt) { if(!scrollBarsInitialized) return; if(evt.getAdjustable() == vertical) setFirstLine(vertical.getValue()); else setHorizontalOffset(-horizontal.getValue()); } //}}} } //}}} //{{{ FocusHandler class private class FocusHandler implements FocusListener { //{{{ focusGained() method @Override public void focusGained(FocusEvent evt) { if(bufferChanging) return; if(match != null) { if(caretLine < match.startLine) invalidateLineRange(caretLine,match.endLine); else invalidateLineRange(match.startLine,caretLine); } else invalidateLine(caretLine); focusedComponent = TextArea.this; } //}}} //{{{ focusLost() method @Override public void focusLost(FocusEvent evt) { if(!isShowing()) return; if(match != null) { if(caretLine < match.startLine) invalidateLineRange(caretLine,match.endLine); else invalidateLineRange(match.startLine,caretLine); } else invalidateLine(caretLine); } //}}} } //}}} //{{{ MouseWheelHandler class private class MouseWheelHandler implements MouseWheelListener { @Override public void mouseWheelMoved(MouseWheelEvent e) { /**************************************************** * move caret depending on pressed control-keys: * - Alt: move cursor, do not select * - Alt+(shift or control): move cursor, select * - shift: scroll horizontally * - control: scroll single line * - <else>: scroll 3 lines ****************************************************/ if(e.isAltDown()) { boolean select = e.isShiftDown() || e.isControlDown(); if(e.getWheelRotation() < 0) goToPrevLine(select); else goToNextLine(select); } else if(e.getScrollType() == MouseWheelEvent.WHEEL_BLOCK_SCROLL) { if(e.isShiftDown()) { // Wheel orientation is reversed so we negate the charwidth _setHorizontalOffset(getHorizontalOffset() + (e.getWheelRotation() > 0 ? 1 : -1) * painter.getWidth()); } else { if(e.getWheelRotation() > 0) scrollDownPage(); else scrollUpPage(); } } else if(e.isControlDown() && e.isShiftDown()) { if(e.getWheelRotation() > 0) scrollDownPage(); else scrollUpPage(); } else if(e.isControlDown()) { setFirstLine(getFirstLine() + e.getWheelRotation()); } else if(e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { if(e.isShiftDown()) { _setHorizontalOffset(getHorizontalOffset() + (-charWidth * e.getUnitsToScroll())); } else { setFirstLine(getFirstLine() + e.getUnitsToScroll()); } } else { if(e.isShiftDown()) { _setHorizontalOffset(getHorizontalOffset() + (-charWidth * e.getWheelRotation())); } else { setFirstLine(getFirstLine() + 3 * e.getWheelRotation()); } } } } //}}} //{{{ RequestFocusLayerUI class private class RequestFocusLayerUI extends LayerUI<JComponent> { //{{{ processMouseEvent() method @Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JComponent> l) { if (e.getID() == MouseEvent.MOUSE_PRESSED) { requestFocus(); } } //}}} //{{{ installUI() method @Override public void installUI(JComponent c) { super.installUI(c); ((JLayer)c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK); } //}}} //{{{ uninstallUI() method @Override public void uninstallUI(JComponent c) { super.uninstallUI(c); ((JLayer)c).setLayerEventMask(0); } //}}} } //}}} //}}} //{{{ Class initializer static { caretTimer = new Timer(500,new CaretBlinker()); caretTimer.setInitialDelay(500); caretTimer.start(); structureTimer = new Timer(100,new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { if(focusedComponent != null) focusedComponent.updateStructureHighlight(); } }); structureTimer.setInitialDelay(100); structureTimer.setRepeats(false); } //}}} public TabExpander getTabExpander() { if(buffer.getBooleanProperty("elasticTabstops")) { return elasticTabstopsExpander; } else { return painter; } } }