| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Tom Eicher (Avaloq Evolution AG) - block selection mode |
| * Markus Schorn <markus.schorn@windriver.com> - shift with trailing empty line - https://bugs.eclipse.org/325438 |
| * Sergey Prigogin (Google) - Bug 441827 - TextViewer.ViewerState.restore method looses caret position |
| * Jonah Graham (Kichwa Coders) - Bug 465684 - Fix initial setVisibleRegion of 0, 0 |
| *******************************************************************************/ |
| package org.eclipse.jface.text; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.PatternSyntaxException; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.LineBackgroundEvent; |
| import org.eclipse.swt.custom.LineBackgroundListener; |
| import org.eclipse.swt.custom.MovementEvent; |
| import org.eclipse.swt.custom.MovementListener; |
| import org.eclipse.swt.custom.ST; |
| import org.eclipse.swt.custom.StyleRange; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.custom.StyledTextPrintOptions; |
| import org.eclipse.swt.custom.VerifyKeyListener; |
| import org.eclipse.swt.dnd.Clipboard; |
| import org.eclipse.swt.dnd.DND; |
| import org.eclipse.swt.dnd.TextTransfer; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.events.VerifyEvent; |
| import org.eclipse.swt.events.VerifyListener; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.printing.PrintDialog; |
| import org.eclipse.swt.printing.Printer; |
| import org.eclipse.swt.printing.PrinterData; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.ScrollBar; |
| import org.eclipse.swt.widgets.Shell; |
| |
| import org.eclipse.core.runtime.Assert; |
| |
| import org.eclipse.text.edits.TextEdit; |
| |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.internal.text.NonDeletingPositionUpdater; |
| import org.eclipse.jface.internal.text.SelectionProcessor; |
| import org.eclipse.jface.internal.text.StickyHoverManager; |
| import org.eclipse.jface.util.Geometry; |
| import org.eclipse.jface.util.OpenStrategy; |
| import org.eclipse.jface.viewers.IPostSelectionProvider; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.ISelectionProvider; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.Viewer; |
| |
| import org.eclipse.jface.text.hyperlink.HyperlinkManager; |
| import org.eclipse.jface.text.hyperlink.HyperlinkManager.DETECTION_STRATEGY; |
| import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; |
| import org.eclipse.jface.text.hyperlink.IHyperlinkDetectorExtension; |
| import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter; |
| import org.eclipse.jface.text.projection.ChildDocument; |
| import org.eclipse.jface.text.projection.ChildDocumentManager; |
| |
| |
| /** |
| * SWT based implementation of {@link ITextViewer} and its extension interfaces. |
| * Once the viewer and its SWT control have been created the viewer can only |
| * indirectly be disposed by disposing its SWT control. |
| * <p> |
| * Clients are supposed to instantiate a text viewer and subsequently to |
| * communicate with it exclusively using the |
| * {@link org.eclipse.jface.text.ITextViewer} interface or any of the |
| * implemented extension interfaces. |
| * <p> |
| * A text viewer serves as text operation target. It only partially supports the |
| * external control of the enable state of its text operations. A text viewer is |
| * also a widget token owner. Anything that wants to display an overlay window |
| * on top of a text viewer should implement the |
| * {@link org.eclipse.jface.text.IWidgetTokenKeeper} interface and participate |
| * in the widget token negotiation between the text viewer and all its potential |
| * widget token keepers. |
| * <p> |
| * This class is not intended to be subclassed outside the JFace Text component.</p> |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class TextViewer extends Viewer implements |
| ITextViewer, ITextViewerExtension, ITextViewerExtension2, ITextViewerExtension4, ITextViewerExtension6, ITextViewerExtension7, ITextViewerExtension8, |
| IEditingSupportRegistry, ITextOperationTarget, ITextOperationTargetExtension, |
| IWidgetTokenOwner, IWidgetTokenOwnerExtension, IPostSelectionProvider { |
| |
| /** Internal flag to indicate the debug state. */ |
| public static final boolean TRACE_ERRORS= false; |
| /** Internal flag to indicate the debug state. */ |
| private static final boolean TRACE_DOUBLE_CLICK= false; |
| |
| // FIXME always use setRedraw to avoid flickering due to scrolling |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=158746 |
| private static final boolean REDRAW_BUG_158746= true; |
| |
| /** |
| * Width constraint for text hovers (in characters). |
| * @since 3.4 |
| */ |
| private static final int TEXT_HOVER_WIDTH_CHARS= 100; //used to be 60 (text font) |
| /** |
| * Height constraint for text hovers (in characters). |
| * @since 3.4 |
| */ |
| private static final int TEXT_HOVER_HEIGHT_CHARS= 12; //used to be 10 (text font) |
| |
| /** |
| * Represents a replace command that brings the text viewer's text widget |
| * back in synchronization with text viewer's document after the document |
| * has been changed. |
| */ |
| protected class WidgetCommand { |
| |
| /** The document event encapsulated by this command. */ |
| public DocumentEvent event; |
| /** The start of the event. */ |
| public int start; |
| /** The length of the event. */ |
| public int length; |
| /** The inserted and replaced text segments of <code>event</code>. */ |
| public String text; |
| /** The replaced text segments of <code>event</code>. */ |
| public String preservedText; |
| |
| /** |
| * Translates a document event into the presentation coordinates of this text viewer. |
| * |
| * @param e the event to be translated |
| */ |
| public void setEvent(DocumentEvent e) { |
| |
| event= e; |
| |
| start= e.getOffset(); |
| length= e.getLength(); |
| text= e.getText(); |
| |
| if (length != 0) { |
| try { |
| |
| if (e instanceof SlaveDocumentEvent) { |
| SlaveDocumentEvent slave= (SlaveDocumentEvent) e; |
| DocumentEvent master= slave.getMasterEvent(); |
| if (master != null) |
| preservedText= master.getDocument().get(master.getOffset(), master.getLength()); |
| } else { |
| preservedText= e.getDocument().get(e.getOffset(), e.getLength()); |
| } |
| |
| } catch (BadLocationException x) { |
| preservedText= null; |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.WidgetCommand.setEvent")); //$NON-NLS-1$ |
| } |
| } else |
| preservedText= null; |
| } |
| } |
| |
| |
| /** |
| * Connects a text double click strategy to this viewer's text widget. |
| * Calls the double click strategies when the mouse has |
| * been clicked inside the text editor. |
| */ |
| class TextDoubleClickStrategyConnector extends MouseAdapter implements MovementListener { |
| |
| /** Internal flag to remember the last double-click selection. */ |
| private Point fDoubleClickSelection; |
| |
| @Override |
| public void mouseUp(MouseEvent e) { |
| fDoubleClickSelection= null; |
| } |
| |
| @Override |
| public void getNextOffset(MovementEvent event) { |
| if (event.movement != SWT.MOVEMENT_WORD_END) |
| return; |
| |
| if (TRACE_DOUBLE_CLICK) { |
| System.out.println("\n+++"); //$NON-NLS-1$ |
| print(event); |
| } |
| |
| if (fDoubleClickSelection != null) { |
| if (fDoubleClickSelection.x <= event.offset && event.offset <= fDoubleClickSelection.y) |
| event.newOffset= fDoubleClickSelection.y; |
| } |
| } |
| |
| @Override |
| public void getPreviousOffset(MovementEvent event) { |
| if (event.movement != SWT.MOVEMENT_WORD_START) |
| return; |
| |
| if (TRACE_DOUBLE_CLICK) { |
| System.out.println("\n---"); //$NON-NLS-1$ |
| print(event); |
| } |
| if (fDoubleClickSelection == null) { |
| ITextDoubleClickStrategy s= (ITextDoubleClickStrategy) selectContentTypePlugin(getSelectedRange().x, fDoubleClickStrategies); |
| if (s != null) { |
| StyledText textWidget= getTextWidget(); |
| s.doubleClicked(TextViewer.this); |
| fDoubleClickSelection= textWidget.getSelection(); |
| event.newOffset= fDoubleClickSelection.x; |
| if (TRACE_DOUBLE_CLICK) |
| System.out.println("- setting selection: x= " + fDoubleClickSelection.x + ", y= " + fDoubleClickSelection.y); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } else { |
| if (fDoubleClickSelection.x <= event.offset && event.offset <= fDoubleClickSelection.y) |
| event.newOffset= fDoubleClickSelection.x; |
| } |
| } |
| } |
| |
| /** |
| * Print trace info about <code>MovementEvent</code>. |
| * |
| * @param e the event to print |
| * @since 3.3 |
| */ |
| private void print(MovementEvent e) { |
| System.out.println("line offset: " + e.lineOffset); //$NON-NLS-1$ |
| System.out.println("line: " + e.lineText); //$NON-NLS-1$ |
| System.out.println("type: " + e.movement); //$NON-NLS-1$ |
| System.out.println("offset: " + e.offset); //$NON-NLS-1$ |
| System.out.println("newOffset: " + e.newOffset); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Monitors the area of the viewer's document that is visible in the viewer. |
| * If the area might have changed, it informs the text viewer about this |
| * potential change and its origin. The origin is internally used for optimization |
| * purposes. |
| */ |
| class ViewportGuard extends MouseAdapter |
| implements ControlListener, KeyListener, SelectionListener { |
| |
| @Override |
| public void controlResized(ControlEvent e) { |
| updateViewportListeners(RESIZE); |
| } |
| |
| @Override |
| public void controlMoved(ControlEvent e) { |
| } |
| |
| /* |
| * @see KeyListener#keyReleased |
| */ |
| @Override |
| public void keyReleased(KeyEvent e) { |
| updateViewportListeners(KEY); |
| } |
| |
| /* |
| * @see KeyListener#keyPressed |
| */ |
| @Override |
| public void keyPressed(KeyEvent e) { |
| updateViewportListeners(KEY); |
| } |
| |
| /* |
| * @see MouseListener#mouseUp |
| */ |
| @Override |
| public void mouseUp(MouseEvent e) { |
| if (fTextWidget != null) |
| fTextWidget.removeSelectionListener(this); |
| updateViewportListeners(MOUSE_END); |
| } |
| |
| /* |
| * @see MouseListener#mouseDown |
| */ |
| @Override |
| public void mouseDown(MouseEvent e) { |
| if (fTextWidget != null) |
| fTextWidget.addSelectionListener(this); |
| } |
| |
| /* |
| * @see SelectionListener#widgetSelected |
| */ |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| if (e.widget == fScroller) |
| updateViewportListeners(SCROLLER); |
| else |
| updateViewportListeners(MOUSE); |
| } |
| |
| /* |
| * @see SelectionListener#widgetDefaultSelected |
| */ |
| @Override |
| public void widgetDefaultSelected(SelectionEvent e) {} |
| } |
| |
| /** |
| * This position updater is used to keep the selection during text shift operations. |
| */ |
| static class ShiftPositionUpdater extends DefaultPositionUpdater { |
| |
| /** |
| * Creates the position updater for the given category. |
| * |
| * @param category the category this updater takes care of |
| */ |
| protected ShiftPositionUpdater(String category) { |
| super(category); |
| } |
| |
| /** |
| * If an insertion happens at the selection's start offset, |
| * the position is extended rather than shifted. |
| */ |
| @Override |
| protected void adaptToInsert() { |
| |
| int myStart= fPosition.offset; |
| int myEnd= fPosition.offset + fPosition.length -1; |
| myEnd= Math.max(myStart, myEnd); |
| |
| int yoursStart= fOffset; |
| |
| if (myEnd < yoursStart) |
| return; |
| |
| if (myStart <= yoursStart) { |
| fPosition.length += fReplaceLength; |
| return; |
| } |
| |
| if (myStart > yoursStart) |
| fPosition.offset += fReplaceLength; |
| } |
| } |
| |
| /** |
| * Internal document listener on the visible document. |
| */ |
| class VisibleDocumentListener implements IDocumentListener { |
| |
| /* |
| * @see IDocumentListener#documentAboutToBeChanged |
| */ |
| @Override |
| public void documentAboutToBeChanged(DocumentEvent e) { |
| if (e.getDocument() == getVisibleDocument()) |
| fWidgetCommand.setEvent(e); |
| handleVisibleDocumentAboutToBeChanged(e); |
| } |
| |
| /* |
| * @see IDocumentListener#documentChanged |
| */ |
| @Override |
| public void documentChanged(DocumentEvent e) { |
| if (fWidgetCommand.event == e) |
| updateTextListeners(fWidgetCommand); |
| fLastSentSelectionChange= null; |
| handleVisibleDocumentChanged(e); |
| } |
| } |
| |
| /** |
| * Internal verify listener. |
| */ |
| class TextVerifyListener implements VerifyListener { |
| |
| /** |
| * Indicates whether verify events are forwarded or ignored. |
| * @since 2.0 |
| */ |
| private boolean fForward= true; |
| |
| /** |
| * Tells the listener to forward received events. |
| * |
| * @param forward <code>true</code> if forwarding should be enabled. |
| * @since 2.0 |
| */ |
| public void forward(boolean forward) { |
| fForward= forward; |
| } |
| |
| @Override |
| public void verifyText(VerifyEvent e) { |
| if (fForward) |
| handleVerifyEvent(e); |
| } |
| } |
| |
| /** |
| * The viewer's manager responsible for registered verify key listeners. |
| * Uses batches rather than robust iterators because of performance issues. |
| * <p> |
| * The implementation is reentrant, i.e. installed listeners may trigger |
| * further <code>VerifyKeyEvent</code>s that may cause other listeners to be |
| * installed, but not thread safe. |
| * </p> |
| * @since 2.0 |
| */ |
| class VerifyKeyListenersManager implements VerifyKeyListener { |
| |
| /** |
| * Represents a batched addListener/removeListener command. |
| */ |
| class Batch { |
| /** The index at which to insert the listener. */ |
| int index; |
| /** The listener to be inserted. */ |
| VerifyKeyListener listener; |
| |
| /** |
| * Creates a new batch containing the given listener for the given index. |
| * |
| * @param l the listener to be added |
| * @param i the index at which to insert the listener |
| */ |
| public Batch(VerifyKeyListener l, int i) { |
| listener= l; |
| index= i; |
| } |
| } |
| |
| /** List of registered verify key listeners. */ |
| private List<VerifyKeyListener> fListeners= new ArrayList<>(); |
| /** List of pending batches. */ |
| private List<Batch> fBatched= new ArrayList<>(); |
| /** The reentrance count. */ |
| private int fReentranceCount= 0; |
| |
| @Override |
| public void verifyKey(VerifyEvent event) { |
| if (fListeners.isEmpty()) |
| return; |
| |
| try { |
| fReentranceCount++; |
| Iterator<VerifyKeyListener> iterator= fListeners.iterator(); |
| while (iterator.hasNext() && event.doit) { |
| VerifyKeyListener listener= iterator.next(); |
| listener.verifyKey(event); // we might trigger reentrant calls on GTK |
| } |
| } finally { |
| fReentranceCount--; |
| } |
| if (fReentranceCount == 0) |
| processBatchedRequests(); |
| } |
| |
| /** |
| * Processes the pending batched requests. |
| */ |
| private void processBatchedRequests() { |
| if (!fBatched.isEmpty()) { |
| Iterator<Batch> e= fBatched.iterator(); |
| while (e.hasNext()) { |
| Batch batch= e.next(); |
| insertListener(batch.listener, batch.index); |
| } |
| fBatched.clear(); |
| } |
| } |
| |
| /** |
| * Returns the number of registered verify key listeners. |
| * |
| * @return the number of registered verify key listeners |
| */ |
| public int numberOfListeners() { |
| return fListeners.size(); |
| } |
| |
| /** |
| * Inserts the given listener at the given index or moves it |
| * to that index. |
| * |
| * @param listener the listener to be inserted |
| * @param index the index of the listener or -1 for remove |
| */ |
| public void insertListener(VerifyKeyListener listener, int index) { |
| |
| if (index == -1) { |
| removeListener(listener); |
| } else if (listener != null) { |
| |
| if (fReentranceCount > 0) { |
| |
| fBatched.add(new Batch(listener, index)); |
| |
| } else { |
| |
| int idx= -1; |
| |
| // find index based on identity |
| int size= fListeners.size(); |
| for (int i= 0; i < size; i++) { |
| if (listener == fListeners.get(i)) { |
| idx= i; |
| break; |
| } |
| } |
| |
| // move or add it |
| if (idx != index) { |
| |
| if (idx != -1) |
| fListeners.remove(idx); |
| |
| if (index > fListeners.size()) |
| fListeners.add(listener); |
| else |
| fListeners.add(index, listener); |
| } |
| |
| if (size == 0) // checking old size, i.e. current size == size + 1 |
| install(); |
| } |
| } |
| } |
| |
| /** |
| * Removes the given listener. |
| * |
| * @param listener the listener to be removed |
| */ |
| public void removeListener(VerifyKeyListener listener) { |
| if (listener == null) |
| return; |
| |
| if (fReentranceCount > 0) { |
| |
| fBatched.add(new Batch(listener, -1)); |
| |
| } else { |
| |
| int size= fListeners.size(); |
| for (int i= 0; i < size; i++) { |
| if (listener == fListeners.get(i)) { |
| fListeners.remove(i); |
| if (size == 1) // checking old size, i.e. current size == size - 1 |
| uninstall(); |
| return; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Installs this manager. |
| */ |
| private void install() { |
| StyledText textWidget= getTextWidget(); |
| if (textWidget != null && !textWidget.isDisposed()) |
| textWidget.addVerifyKeyListener(this); |
| } |
| |
| /** |
| * Uninstalls this manager. |
| */ |
| private void uninstall() { |
| StyledText textWidget= getTextWidget(); |
| if (textWidget != null && !textWidget.isDisposed()) |
| textWidget.removeVerifyKeyListener(this); |
| } |
| } |
| |
| |
| /** |
| * Reification of a range in which a find replace operation is performed. This range is visually |
| * highlighted in the viewer as long as the replace operation is in progress. |
| * |
| * @since 2.0 |
| */ |
| class FindReplaceRange implements LineBackgroundListener, ITextListener, IPositionUpdater { |
| |
| /** Internal name for the position category used to update the range. */ |
| private final static String RANGE_CATEGORY= "org.eclipse.jface.text.TextViewer.find.range"; //$NON-NLS-1$ |
| |
| /** The highlight color of this range. */ |
| private Color fHighlightColor; |
| /** The position used to lively update this range's extent. */ |
| private Position fPosition; |
| |
| /** Creates a new find/replace range with the given extent. |
| * |
| * @param range the extent of this range |
| */ |
| public FindReplaceRange(IRegion range) { |
| setRange(range); |
| } |
| |
| /** |
| * Sets the extent of this range. |
| * |
| * @param range the extent of this range |
| */ |
| public void setRange(IRegion range) { |
| fPosition= new Position(range.getOffset(), range.getLength()); |
| } |
| |
| /** |
| * Returns the extent of this range. |
| * |
| * @return the extent of this range |
| */ |
| public IRegion getRange() { |
| return new Region(fPosition.getOffset(), fPosition.getLength()); |
| } |
| |
| /** |
| * Sets the highlight color of this range. Causes the range to be redrawn. |
| * |
| * @param color the highlight color |
| */ |
| public void setHighlightColor(Color color) { |
| fHighlightColor= color; |
| paint(); |
| } |
| |
| @Override |
| public void lineGetBackground(LineBackgroundEvent event) { |
| /* Don't use cached line information because of patched redrawing events. */ |
| |
| if (fTextWidget != null) { |
| int offset= widgetOffset2ModelOffset(event.lineOffset); |
| if (fPosition.includes(offset)) |
| event.lineBackground= fHighlightColor; |
| } |
| } |
| |
| /** |
| * Installs this range. The range registers itself as background |
| * line painter and text listener. Also, it creates a category with the |
| * viewer's document to maintain its own extent. |
| */ |
| public void install() { |
| TextViewer.this.addTextListener(this); |
| fTextWidget.addLineBackgroundListener(this); |
| |
| IDocument document= TextViewer.this.getDocument(); |
| try { |
| document.addPositionCategory(RANGE_CATEGORY); |
| document.addPosition(RANGE_CATEGORY, fPosition); |
| document.addPositionUpdater(this); |
| } catch (BadPositionCategoryException e) { |
| // should not happen |
| } catch (BadLocationException e) { |
| // should not happen |
| } |
| |
| paint(); |
| } |
| |
| /** |
| * Uninstalls this range. |
| * @see #install() |
| */ |
| public void uninstall() { |
| |
| // http://bugs.eclipse.org/bugs/show_bug.cgi?id=19612 |
| |
| IDocument document= TextViewer.this.getDocument(); |
| if (document != null) { |
| document.removePositionUpdater(this); |
| document.removePosition(fPosition); |
| } |
| |
| if (fTextWidget != null && !fTextWidget.isDisposed()) |
| fTextWidget.removeLineBackgroundListener(this); |
| |
| TextViewer.this.removeTextListener(this); |
| |
| clear(); |
| } |
| |
| /** |
| * Clears the highlighting of this range. |
| */ |
| private void clear() { |
| if (fTextWidget != null && !fTextWidget.isDisposed()) |
| fTextWidget.redraw(); |
| } |
| |
| /** |
| * Paints the highlighting of this range. |
| */ |
| private void paint() { |
| |
| IRegion widgetRegion= modelRange2WidgetRange(fPosition); |
| int offset= widgetRegion.getOffset(); |
| int length= widgetRegion.getLength(); |
| |
| int count= fTextWidget.getCharCount(); |
| if (offset + length >= count) { |
| length= count - offset; // clip |
| |
| Point upperLeft= fTextWidget.getLocationAtOffset(offset); |
| Point lowerRight= fTextWidget.getLocationAtOffset(offset + length); |
| int width= fTextWidget.getClientArea().width; |
| int height= fTextWidget.getLineHeight(offset + length) + lowerRight.y - upperLeft.y; |
| fTextWidget.redraw(upperLeft.x, upperLeft.y, width, height, false); |
| } |
| |
| fTextWidget.redrawRange(offset, length, true); |
| } |
| |
| @Override |
| public void textChanged(TextEvent event) { |
| if (event.getViewerRedrawState()) |
| paint(); |
| } |
| |
| @Override |
| public void update(DocumentEvent event) { |
| int offset= event.getOffset(); |
| int length= event.getLength(); |
| int delta= event.getText().length() - length; |
| |
| if (offset < fPosition.getOffset()) |
| fPosition.setOffset(fPosition.getOffset() + delta); |
| else if (offset < fPosition.getOffset() + fPosition.getLength()) |
| fPosition.setLength(fPosition.getLength() + delta); |
| } |
| } |
| |
| /** |
| * This viewer's find/replace target. |
| */ |
| class FindReplaceTarget implements IFindReplaceTarget, IFindReplaceTargetExtension, IFindReplaceTargetExtension3 { |
| |
| /** The range for this target. */ |
| private FindReplaceRange fRange; |
| /** The highlight color of the range of this target. */ |
| private Color fScopeHighlightColor; |
| /** The document partitioner remembered in case of a "Replace All". */ |
| private Map<String, IDocumentPartitioner> fRememberedPartitioners; |
| /** |
| * The active rewrite session. |
| * @since 3.1 |
| */ |
| private DocumentRewriteSession fRewriteSession; |
| |
| @Override |
| public String getSelectionText() { |
| Point s= TextViewer.this.getSelectedRange(); |
| if (s.x > -1 && s.y > -1) { |
| try { |
| IDocument document= TextViewer.this.getDocument(); |
| return document.get(s.x, s.y); |
| } catch (BadLocationException x) { |
| } |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public void replaceSelection(String text) { |
| replaceSelection(text, false); |
| } |
| |
| @Override |
| public void replaceSelection(String text, boolean regExReplace) { |
| Point s= TextViewer.this.getSelectedRange(); |
| if (s.x > -1 && s.y > -1) { |
| try { |
| IRegion matchRegion= TextViewer.this.getFindReplaceDocumentAdapter().replace(text, regExReplace); |
| int length= -1; |
| if (matchRegion != null) |
| length= matchRegion.getLength(); |
| |
| if (text != null && length > 0) |
| TextViewer.this.setSelectedRange(s.x, length); |
| } catch (BadLocationException x) { |
| } |
| } |
| } |
| |
| @Override |
| public boolean isEditable() { |
| return TextViewer.this.isEditable(); |
| } |
| |
| @Override |
| public Point getSelection() { |
| Point modelSelection= TextViewer.this.getSelectedRange(); |
| Point widgetSelection= modelSelection2WidgetSelection(modelSelection); |
| return widgetSelection != null ? widgetSelection : new Point(-1, -1); |
| } |
| |
| @Override |
| public int findAndSelect(int widgetOffset, String findString, boolean searchForward, boolean caseSensitive, boolean wholeWord) { |
| try { |
| return findAndSelect(widgetOffset, findString, searchForward, caseSensitive, wholeWord, false); |
| } catch (PatternSyntaxException x) { |
| return -1; |
| } |
| } |
| |
| @Override |
| public int findAndSelect(int widgetOffset, String findString, boolean searchForward, boolean caseSensitive, boolean wholeWord, boolean regExSearch) { |
| |
| int modelOffset= widgetOffset == -1 ? -1 : widgetOffset2ModelOffset(widgetOffset); |
| |
| if (fRange != null) { |
| IRegion range= fRange.getRange(); |
| modelOffset= TextViewer.this.findAndSelectInRange(modelOffset, findString, searchForward, caseSensitive, wholeWord, range.getOffset(), range.getLength(), regExSearch); |
| } else { |
| modelOffset= TextViewer.this.findAndSelect(modelOffset, findString, searchForward, caseSensitive, wholeWord, regExSearch); |
| } |
| |
| widgetOffset= modelOffset == -1 ? -1 : modelOffset2WidgetOffset(modelOffset); |
| return widgetOffset; |
| } |
| |
| @Override |
| public boolean canPerformFind() { |
| return TextViewer.this.canPerformFind(); |
| } |
| |
| @Override |
| public void beginSession() { |
| fRange= null; |
| } |
| |
| @Override |
| public void endSession() { |
| if (fRange != null) { |
| fRange.uninstall(); |
| fRange= null; |
| } |
| } |
| |
| @Override |
| public IRegion getScope() { |
| return fRange == null ? null : fRange.getRange(); |
| } |
| |
| @Override |
| public Point getLineSelection() { |
| Point point= TextViewer.this.getSelectedRange(); |
| |
| try { |
| IDocument document= TextViewer.this.getDocument(); |
| |
| // beginning of line |
| int line= document.getLineOfOffset(point.x); |
| int offset= document.getLineOffset(line); |
| |
| // end of line |
| IRegion lastLineInfo= document.getLineInformationOfOffset(point.x + point.y); |
| int lastLine= document.getLineOfOffset(point.x + point.y); |
| int length; |
| if (lastLineInfo.getOffset() == point.x + point.y && lastLine > 0) |
| length= document.getLineOffset(lastLine - 1) + document.getLineLength(lastLine - 1) - offset; |
| else |
| length= lastLineInfo.getOffset() + lastLineInfo.getLength() - offset; |
| |
| return new Point(offset, length); |
| |
| } catch (BadLocationException e) { |
| // should not happen |
| return new Point(point.x, 0); |
| } |
| } |
| |
| @Override |
| public void setSelection(int modelOffset, int modelLength) { |
| TextViewer.this.setSelectedRange(modelOffset, modelLength); |
| } |
| |
| @Override |
| public void setScope(IRegion scope) { |
| if (fRange != null) |
| fRange.uninstall(); |
| |
| if (scope == null) { |
| fRange= null; |
| return; |
| } |
| |
| fRange= new FindReplaceRange(scope); |
| fRange.setHighlightColor(fScopeHighlightColor); |
| fRange.install(); |
| } |
| |
| @Override |
| public void setScopeHighlightColor(Color color) { |
| if (fRange != null) |
| fRange.setHighlightColor(color); |
| fScopeHighlightColor= color; |
| } |
| |
| @Override |
| public void setReplaceAllMode(boolean replaceAll) { |
| |
| // http://bugs.eclipse.org/bugs/show_bug.cgi?id=18232 |
| |
| IDocument document= TextViewer.this.getDocument(); |
| |
| if (replaceAll) { |
| |
| if (document instanceof IDocumentExtension4) { |
| IDocumentExtension4 extension= (IDocumentExtension4) document; |
| fRewriteSession= extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL); |
| } else { |
| TextViewer.this.setRedraw(false); |
| TextViewer.this.startSequentialRewriteMode(false); |
| |
| if (fUndoManager != null) |
| fUndoManager.beginCompoundChange(); |
| |
| fRememberedPartitioners= TextUtilities.removeDocumentPartitioners(document); |
| } |
| |
| } else { |
| |
| if (document instanceof IDocumentExtension4) { |
| IDocumentExtension4 extension= (IDocumentExtension4) document; |
| extension.stopRewriteSession(fRewriteSession); |
| } else { |
| TextViewer.this.setRedraw(true); |
| TextViewer.this.stopSequentialRewriteMode(); |
| |
| if (fUndoManager != null) |
| fUndoManager.endCompoundChange(); |
| |
| if (fRememberedPartitioners != null) |
| TextUtilities.addDocumentPartitioners(document, fRememberedPartitioners); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * The viewer's rewrite target. |
| * @since 2.0 |
| */ |
| class RewriteTarget implements IRewriteTarget { |
| |
| @Override |
| public void beginCompoundChange() { |
| if (fUndoManager != null) |
| fUndoManager.beginCompoundChange(); |
| } |
| |
| @Override |
| public void endCompoundChange() { |
| if (fUndoManager != null) |
| fUndoManager.endCompoundChange(); |
| } |
| |
| @Override |
| public IDocument getDocument() { |
| return TextViewer.this.getDocument(); |
| } |
| |
| @Override |
| public void setRedraw(boolean redraw) { |
| TextViewer.this.setRedraw(redraw); |
| } |
| } |
| |
| /** |
| * Value object used as key in the text hover configuration table. It is |
| * modifiable only inside this compilation unit to allow the reuse of created |
| * objects for efficiency reasons |
| * |
| * @since 2.1 |
| */ |
| protected class TextHoverKey { |
| |
| /** The content type this key belongs to */ |
| private String fContentType; |
| /** The state mask */ |
| private int fStateMask; |
| |
| /** |
| * Creates a new text hover key for the given content type and state mask. |
| * |
| * @param contentType the content type |
| * @param stateMask the state mask |
| */ |
| protected TextHoverKey(String contentType, int stateMask) { |
| Assert.isNotNull(contentType); |
| fContentType= contentType; |
| fStateMask= stateMask; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || obj.getClass() != getClass()) |
| return false; |
| TextHoverKey textHoverKey= (TextHoverKey)obj; |
| return textHoverKey.fContentType.equals(fContentType) && textHoverKey.fStateMask == fStateMask; |
| } |
| |
| @Override |
| public int hashCode() { |
| return fStateMask << 16 | fContentType.hashCode(); |
| } |
| |
| /** |
| * Sets the state mask of this text hover key. |
| * |
| * @param stateMask the state mask |
| */ |
| private void setStateMask(int stateMask) { |
| fStateMask= stateMask; |
| } |
| } |
| |
| /** |
| * Position storing block selection information in order to maintain a column selection. |
| * |
| * @since 3.5 |
| */ |
| private static final class ColumnPosition extends Position { |
| int fStartColumn, fEndColumn; |
| ColumnPosition(int offset, int length, int startColumn, int endColumn) { |
| super(offset, length); |
| fStartColumn= startColumn; |
| fEndColumn= endColumn; |
| } |
| } |
| |
| /** |
| * Captures and remembers the viewer state (selection and visual position). {@link TextViewer.ViewerState} |
| * instances are normally used once and then discarded, similar to the following snippet: |
| * <pre> |
| * ViewerState state= new ViewerState(); // remember the state |
| * doStuff(); // operation that may call setRedraw() and perform complex document modifications |
| * state.restore(true); // restore the remembered state |
| * </pre> |
| * |
| * @since 3.3 |
| */ |
| private final class ViewerState { |
| /** The position tracking the selection. */ |
| private Position fSelection; |
| /** <code>true</code> if {@link #fSelection} was originally backwards. */ |
| private boolean fReverseSelection; |
| /** <code>true</code> if the selection has been updated while in redraw(off) mode. */ |
| private boolean fSelectionSet; |
| /** The position tracking the visually stable line. */ |
| private Position fStableLine; |
| /** The pixel offset of the stable line measured from the client area. */ |
| private int fStablePixel; |
| |
| /** The position updater for {@link #fSelection} and {@link #fStableLine}. */ |
| private IPositionUpdater fUpdater; |
| /** The document that the position updater and the positions are registered with. */ |
| private IDocument fUpdaterDocument; |
| /** The position category used by {@link #fUpdater}. */ |
| private String fUpdaterCategory; |
| |
| /** |
| * Creates a new viewer state instance and connects it to the current document. |
| */ |
| public ViewerState() { |
| IDocument document= getDocument(); |
| if (document != null) |
| connect(document); |
| } |
| |
| /** |
| * Returns the normalized selection, i.e. the the selection length is always non-negative. |
| * |
| * @return the normalized selection |
| */ |
| public Point getSelection() { |
| if (fSelection == null) |
| return new Point(-1, -1); |
| return new Point(fSelection.getOffset(), fSelection.getLength()); |
| } |
| |
| /** |
| * Updates the selection. |
| * |
| * @param offset the new selection offset |
| * @param length the new selection length |
| */ |
| public void updateSelection(int offset, int length) { |
| fSelectionSet= true; |
| if (fSelection == null) |
| fSelection= new Position(offset, length); |
| else |
| updatePosition(fSelection, offset, length); |
| } |
| |
| /** |
| * Restores the state and disconnects it from the document. The selection is no longer |
| * tracked after this call. |
| * |
| * @param restoreViewport <code>true</code> to restore both selection and viewport, |
| * <code>false</code> to only restore the selection |
| */ |
| public void restore(boolean restoreViewport) { |
| if (isConnected()) |
| disconnect(); |
| if (fSelection != null) { |
| if (fSelection instanceof ColumnPosition) { |
| ColumnPosition cp= (ColumnPosition)fSelection; |
| IDocument document= fDocument; |
| try { |
| int startLine= document.getLineOfOffset(fSelection.getOffset()); |
| int startLineOffset= document.getLineOffset(startLine); |
| int selectionEnd= fSelection.getOffset() + fSelection.getLength(); |
| int endLine= document.getLineOfOffset(selectionEnd); |
| int endLineOffset= document.getLineOffset(endLine); |
| int tabs= getTextWidget().getTabs(); |
| int startColumn= fSelection.getOffset() - startLineOffset + cp.fStartColumn; |
| int endColumn= selectionEnd - endLineOffset + cp.fEndColumn; |
| setSelection(new BlockTextSelection(document, startLine, startColumn, endLine, endColumn, tabs)); |
| } catch (BadLocationException e) { |
| // fall back to linear mode |
| setSelectedRange(cp.getOffset(), cp.getLength()); |
| } |
| } else { |
| int offset= fSelection.getOffset(); |
| int length= fSelection.getLength(); |
| if (fReverseSelection) { |
| offset+= length; |
| length= -length; |
| } |
| setSelectedRange(offset, length); |
| } |
| if (restoreViewport) |
| updateViewport(); |
| } |
| } |
| |
| /** |
| * Updates the viewport, trying to keep the |
| * {@linkplain StyledText#getLinePixel(int) line pixel} of the caret line stable. If the |
| * selection has been updated while in redraw(false) mode, the new selection is revealed. |
| */ |
| private void updateViewport() { |
| if (fSelectionSet) { |
| revealRange(fSelection.getOffset(), fSelection.getLength()); |
| } else if (fStableLine != null) { |
| int stableLine; |
| try { |
| stableLine= fUpdaterDocument.getLineOfOffset(fStableLine.getOffset()); |
| } catch (BadLocationException x) { |
| // ignore and return silently |
| return; |
| } |
| int stableWidgetLine= getClosestWidgetLineForModelLine(stableLine); |
| if (stableWidgetLine == -1) |
| return; |
| int linePixel= getTextWidget().getLinePixel(stableWidgetLine); |
| int delta= fStablePixel - linePixel; |
| int topPixel= getTextWidget().getTopPixel(); |
| getTextWidget().setTopPixel(topPixel - delta); |
| } |
| } |
| |
| /** |
| * Remembers the viewer state. |
| * |
| * @param document the document to remember the state of |
| */ |
| private void connect(IDocument document) { |
| Assert.isLegal(document != null); |
| Assert.isLegal(!isConnected()); |
| fUpdaterDocument= document; |
| try { |
| fUpdaterCategory= SELECTION_POSITION_CATEGORY + hashCode(); |
| fUpdater= new NonDeletingPositionUpdater(fUpdaterCategory); |
| fUpdaterDocument.addPositionCategory(fUpdaterCategory); |
| fUpdaterDocument.addPositionUpdater(fUpdater); |
| |
| ISelection selection= TextViewer.this.getSelection(); |
| if (selection instanceof IBlockTextSelection) { |
| IBlockTextSelection bts= (IBlockTextSelection) selection; |
| int startVirtual= Math.max(0, bts.getStartColumn() - document.getLineInformationOfOffset(bts.getOffset()).getLength()); |
| int endVirtual= Math.max(0, bts.getEndColumn() - document.getLineInformationOfOffset(bts.getOffset() + bts.getLength()).getLength()); |
| fSelection= new ColumnPosition(bts.getOffset(), bts.getLength(), startVirtual, endVirtual); |
| } else { |
| Point range= fTextWidget.getSelectionRange(); |
| int caretOffset= fTextWidget.getCaretOffset(); |
| fReverseSelection= caretOffset == range.x; |
| Point selectionRange= getSelectedRange(); |
| fSelection= new Position(selectionRange.x, selectionRange.y); |
| } |
| |
| fSelectionSet= false; |
| fUpdaterDocument.addPosition(fUpdaterCategory, fSelection); |
| |
| int stableLine= getStableLine(); |
| int stableWidgetLine= modelLine2WidgetLine(stableLine); |
| fStablePixel= getTextWidget().getLinePixel(stableWidgetLine); |
| IRegion stableLineInfo= fUpdaterDocument.getLineInformation(stableLine); |
| fStableLine= new Position(stableLineInfo.getOffset(), stableLineInfo.getLength()); |
| fUpdaterDocument.addPosition(fUpdaterCategory, fStableLine); |
| } catch (BadPositionCategoryException e) { |
| // cannot happen |
| Assert.isTrue(false); |
| } catch (BadLocationException e) { |
| // should not happen except on concurrent modification |
| // ignore and disconnect |
| disconnect(); |
| } |
| } |
| |
| /** |
| * Updates a position with the given information and clears its deletion state. |
| * |
| * @param position the position to update |
| * @param offset the new selection offset |
| * @param length the new selection length |
| */ |
| private void updatePosition(Position position, int offset, int length) { |
| position.setOffset(offset); |
| position.setLength(length); |
| // http://bugs.eclipse.org/bugs/show_bug.cgi?id=32795 |
| position.isDeleted= false; |
| } |
| |
| /** |
| * Returns the document line to keep visually stable. If the caret line is (partially) |
| * visible, it is returned, otherwise the topmost (partially) visible line is returned. |
| * |
| * @return the visually stable line of this viewer state |
| */ |
| private int getStableLine() { |
| int stableLine; // the model line that we try to keep stable |
| int caretLine= getTextWidget().getLineAtOffset(getTextWidget().getCaretOffset()); |
| if (caretLine < JFaceTextUtil.getPartialTopIndex(getTextWidget()) || caretLine > JFaceTextUtil.getPartialBottomIndex(getTextWidget())) { |
| stableLine= JFaceTextUtil.getPartialTopIndex(TextViewer.this); |
| } else { |
| stableLine= widgetLine2ModelLine(caretLine); |
| } |
| return stableLine; |
| } |
| |
| /** |
| * Returns <code>true</code> if the viewer state is being tracked, <code>false</code> |
| * otherwise. |
| * |
| * @return the tracking state |
| */ |
| private boolean isConnected() { |
| return fUpdater != null; |
| } |
| |
| /** |
| * Disconnects from the document. |
| */ |
| private void disconnect() { |
| Assert.isTrue(isConnected()); |
| try { |
| fUpdaterDocument.removePosition(fUpdaterCategory, fSelection); |
| fUpdaterDocument.removePosition(fUpdaterCategory, fStableLine); |
| fUpdaterDocument.removePositionUpdater(fUpdater); |
| fUpdater= null; |
| fUpdaterDocument.removePositionCategory(fUpdaterCategory); |
| fUpdaterCategory= null; |
| } catch (BadPositionCategoryException x) { |
| // cannot happen |
| Assert.isTrue(false); |
| } |
| } |
| } |
| |
| /** |
| * Internal cursor listener i.e. aggregation of mouse and key listener. |
| * |
| * @since 3.0 |
| */ |
| private class CursorListener implements KeyListener, MouseListener { |
| |
| /** |
| * Installs this cursor listener. |
| */ |
| private void install() { |
| if (fTextWidget != null && !fTextWidget.isDisposed()) { |
| fTextWidget.addKeyListener(this); |
| fTextWidget.addMouseListener(this); |
| } |
| } |
| |
| /** |
| * Uninstalls this cursor listener. |
| */ |
| private void uninstall() { |
| if (fTextWidget != null && !fTextWidget.isDisposed()) { |
| fTextWidget.removeKeyListener(this); |
| fTextWidget.removeMouseListener(this); |
| } |
| } |
| |
| @Override |
| public void keyPressed(KeyEvent event) { |
| } |
| |
| /* |
| * @see KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent) |
| */ |
| @Override |
| public void keyReleased(KeyEvent e) { |
| if (!fTextWidget.isTextSelected()) { |
| fLastSentSelectionChange= null; |
| queuePostSelectionChanged(e.character == SWT.DEL); |
| } |
| } |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent e) { |
| } |
| |
| @Override |
| public void mouseDown(MouseEvent e) { |
| } |
| |
| @Override |
| public void mouseUp(MouseEvent event) { |
| if (!fTextWidget.isTextSelected()) |
| queuePostSelectionChanged(false); |
| } |
| } |
| |
| /** |
| * Internal listener to document rewrite session state changes. |
| * @since 3.1 |
| */ |
| private class DocumentRewriteSessionListener implements IDocumentRewriteSessionListener { |
| |
| @Override |
| public void documentRewriteSessionChanged(DocumentRewriteSessionEvent event) { |
| IRewriteTarget target= TextViewer.this.getRewriteTarget(); |
| final boolean toggleRedraw; |
| if (REDRAW_BUG_158746) |
| toggleRedraw= true; |
| else |
| toggleRedraw= event.getSession().getSessionType() != DocumentRewriteSessionType.UNRESTRICTED_SMALL; |
| final boolean viewportStabilize= !toggleRedraw; |
| if (DocumentRewriteSessionEvent.SESSION_START == event.getChangeType()) { |
| if (toggleRedraw) |
| target.setRedraw(false); |
| target.beginCompoundChange(); |
| if (viewportStabilize && fViewerState == null) |
| fViewerState= new ViewerState(); |
| } else if (DocumentRewriteSessionEvent.SESSION_STOP == event.getChangeType()) { |
| if (viewportStabilize && fViewerState != null) { |
| fViewerState.restore(true); |
| fViewerState= null; |
| } |
| target.endCompoundChange(); |
| if (toggleRedraw) |
| target.setRedraw(true); |
| } |
| } |
| } |
| |
| |
| /** |
| * Identifies the scrollbars as originators of a view port change. |
| */ |
| protected static final int SCROLLER= 1; |
| /** |
| * Identifies mouse moves as originators of a view port change. |
| */ |
| protected static final int MOUSE= 2; |
| /** |
| * Identifies mouse button up as originator of a view port change. |
| */ |
| protected static final int MOUSE_END= 3; |
| /** |
| * Identifies key strokes as originators of a view port change. |
| */ |
| protected static final int KEY= 4; |
| /** |
| * Identifies window resizing as originator of a view port change. |
| */ |
| protected static final int RESIZE= 5; |
| /** |
| * Identifies internal reasons as originators of a view port change. |
| */ |
| protected static final int INTERNAL= 6; |
| |
| /** Internal name of the position category used selection preservation during shift. */ |
| protected static final String SHIFTING= "__TextViewer_shifting"; //$NON-NLS-1$ |
| |
| /** |
| * Base position category name used by the selection updater |
| * @since 3.1 |
| */ |
| private static final String SELECTION_POSITION_CATEGORY= "_textviewer_selection_category"; //$NON-NLS-1$ |
| |
| /** |
| * The shared printer data. |
| * |
| * @since 3.6 |
| */ |
| private static PrinterData fgPrinterData= null; |
| |
| /** The viewer's text widget */ |
| private StyledText fTextWidget; |
| /** The viewer's input document */ |
| private IDocument fDocument; |
| /** The viewer's visible document */ |
| private IDocument fVisibleDocument; |
| /** The viewer's document adapter */ |
| private IDocumentAdapter fDocumentAdapter; |
| /** The slave document manager */ |
| private ISlaveDocumentManager fSlaveDocumentManager; |
| /** The text viewer's double click strategies connector */ |
| private TextDoubleClickStrategyConnector fDoubleClickStrategyConnector; |
| /** The text viewer's view port guard */ |
| private ViewportGuard fViewportGuard; |
| /** Caches the graphical coordinate of the first visible line */ |
| private int fTopInset= 0; |
| /** The most recent document modification as widget command */ |
| private WidgetCommand fWidgetCommand= new WidgetCommand(); |
| /** The SWT control's scrollbars */ |
| private ScrollBar fScroller; |
| /** Listener on the visible document */ |
| private VisibleDocumentListener fVisibleDocumentListener= new VisibleDocumentListener(); |
| /** Verify listener */ |
| private TextVerifyListener fVerifyListener= new TextVerifyListener(); |
| /** The most recent widget modification as document command */ |
| private DocumentCommand fDocumentCommand= new DocumentCommand(); |
| /** The viewer's find/replace target */ |
| private IFindReplaceTarget fFindReplaceTarget; |
| /** |
| * The text viewer's hovering controller |
| * @since 2.0 |
| */ |
| private TextViewerHoverManager fTextHoverManager; |
| /** |
| * The viewer widget token keeper |
| * @since 2.0 |
| */ |
| private IWidgetTokenKeeper fWidgetTokenKeeper; |
| /** |
| * The viewer's manager of verify key listeners |
| * @since 2.0 |
| */ |
| private VerifyKeyListenersManager fVerifyKeyListenersManager= new VerifyKeyListenersManager(); |
| /** |
| * The mark position. |
| * @since 2.0 |
| */ |
| protected Position fMarkPosition; |
| /** |
| * The mark position category. |
| * @since 2.0 |
| */ |
| private final String MARK_POSITION_CATEGORY="__mark_category_" + hashCode(); //$NON-NLS-1$ |
| /** |
| * The mark position updater |
| * @since 2.0 |
| */ |
| private final IPositionUpdater fMarkPositionUpdater= new DefaultPositionUpdater(MARK_POSITION_CATEGORY); |
| /** |
| * The flag indicating the redraw behavior |
| * @since 2.0 |
| */ |
| private int fRedrawCounter= 0; |
| /** |
| * The viewer's rewrite target |
| * @since 2.0 |
| */ |
| private IRewriteTarget fRewriteTarget; |
| /** |
| * The viewer's cursor listener. |
| * @since 3.0 |
| */ |
| private CursorListener fCursorListener; |
| /** |
| * Last selection range sent to selection change listeners. |
| * @since 3.0 |
| */ |
| private IRegion fLastSentSelectionChange; |
| /** |
| * The registered post selection changed listeners. |
| * @since 3.0 |
| */ |
| private List<ISelectionChangedListener> fPostSelectionChangedListeners; |
| /** |
| * Queued post selection changed events count. |
| * @since 3.0 |
| */ |
| private final int[] fNumberOfPostSelectionChangedEvents= new int[1]; |
| /** |
| * Last selection range sent to post selection change listeners. |
| * @since 3.0 |
| */ |
| private IRegion fLastSentPostSelectionChange; |
| /** |
| * <code>true</code> iff a post selection event must be fired even if the selection didn't change |
| * @since 3.11 |
| */ |
| private boolean fFireEqualPostSelectionChange; |
| /** |
| * The set of registered editor helpers. |
| * @since 3.1 |
| */ |
| private Set<IEditingSupport> fEditorHelpers= new HashSet<>(); |
| /** |
| * The internal rewrite session listener. |
| * @since 3.1 |
| */ |
| private DocumentRewriteSessionListener fDocumentRewriteSessionListener= new DocumentRewriteSessionListener(); |
| |
| /** Should the auto indent strategies ignore the next edit operation */ |
| protected boolean fIgnoreAutoIndent= false; |
| /** The strings a line is prefixed with on SHIFT_RIGHT and removed from each line on SHIFT_LEFT */ |
| protected Map<String, String[]> fIndentChars; |
| /** The string a line is prefixed with on PREFIX and removed from each line on STRIP_PREFIX */ |
| protected Map<String, String[]> fDefaultPrefixChars; |
| /** The text viewer's text double click strategies */ |
| protected Map<String, ITextDoubleClickStrategy> fDoubleClickStrategies; |
| /** The text viewer's undo manager */ |
| protected IUndoManager fUndoManager; |
| /** The text viewer's auto indent strategies */ |
| protected Map<String, List<IAutoEditStrategy>> fAutoIndentStrategies; |
| /** The text viewer's text hovers */ |
| protected Map<TextHoverKey, ITextHover> fTextHovers; |
| /** All registered view port listeners> */ |
| protected List<IViewportListener> fViewportListeners; |
| /** The last visible vertical position of the top line */ |
| protected int fLastTopPixel; |
| /** All registered text listeners */ |
| protected List<ITextListener> fTextListeners; |
| /** All registered text input listeners */ |
| protected List<ITextInputListener> fTextInputListeners; |
| /** The text viewer's event consumer */ |
| protected IEventConsumer fEventConsumer; |
| /** Indicates whether the viewer's text presentation should be replaced are modified. */ |
| protected boolean fReplaceTextPresentation= false; |
| /** |
| * The creator of the text hover control |
| * @since 2.0 |
| */ |
| protected IInformationControlCreator fHoverControlCreator; |
| /** |
| * The mapping between model and visible document. |
| * @since 2.1 |
| */ |
| protected IDocumentInformationMapping fInformationMapping; |
| /** |
| * The viewer's paint manager. |
| * @since 2.1 |
| */ |
| protected PaintManager fPaintManager; |
| /** |
| * The viewers partitioning. I.e. the partitioning name the viewer uses to access partitioning information of its input document. |
| * @since 3.0 |
| */ |
| protected String fPartitioning; |
| /** |
| * All registered text presentation listeners. |
| * since 3.0 |
| */ |
| protected List<ITextPresentationListener> fTextPresentationListeners; |
| /** |
| * The find/replace document adapter. |
| * @since 3.0 |
| */ |
| protected FindReplaceDocumentAdapter fFindReplaceDocumentAdapter; |
| /** |
| * The text viewer's hyperlink detectors. |
| * @since 3.1 |
| */ |
| protected IHyperlinkDetector[] fHyperlinkDetectors; |
| /** |
| * The text viewer's hyperlink presenter. |
| * @since 3.1 |
| */ |
| protected IHyperlinkPresenter fHyperlinkPresenter; |
| /** |
| * The text viewer's hyperlink manager. |
| * @since 3.1 |
| */ |
| protected HyperlinkManager fHyperlinkManager; |
| /** |
| * The SWT key modifier mask which in combination |
| * with the left mouse button triggers the hyperlink mode. |
| * @since 3.1 |
| */ |
| protected int fHyperlinkStateMask; |
| /** |
| * The viewer state when in non-redraw state, <code>null</code> otherwise. |
| * @since 3.3 |
| */ |
| private ViewerState fViewerState; |
| /** |
| * The editor's tab converter. |
| * @since 3.3 |
| */ |
| private IAutoEditStrategy fTabsToSpacesConverter; |
| /** |
| * The last verify event time, used to fold block editing events. |
| * @since 3.5 |
| */ |
| private int fLastEventTime; |
| /** |
| * Pointer to disposed control. |
| * |
| * @since 3.8 |
| */ |
| private Control fDisposedControl; |
| |
| |
| //---- Construction and disposal ------------------ |
| |
| |
| /** |
| * Internal use only |
| */ |
| protected TextViewer() { |
| } |
| |
| /** |
| * Create a new text viewer with the given SWT style bits. |
| * The viewer is ready to use but does not have any plug-in installed. |
| * |
| * @param parent the parent of the viewer's control |
| * @param styles the SWT style bits for the viewer's control, |
| * <em>if <code>SWT.WRAP</code> is set then a custom document adapter needs to be provided, see {@link #createDocumentAdapter()} |
| */ |
| public TextViewer(Composite parent, int styles) { |
| createControl(parent, styles); |
| } |
| |
| /** |
| * Factory method to create the text widget to be used as the viewer's text widget. |
| * |
| * @param parent the parent of the styled text |
| * @param styles the styles for the styled text |
| * @return the text widget to be used |
| */ |
| protected StyledText createTextWidget(Composite parent, int styles) { |
| StyledText styledText= new StyledText(parent, styles); |
| styledText.setLeftMargin(Math.max(styledText.getLeftMargin(), 2)); |
| return styledText; |
| } |
| |
| /** |
| * Factory method to create the document adapter to be used by this viewer. |
| * |
| * @return the document adapter to be used |
| */ |
| protected IDocumentAdapter createDocumentAdapter() { |
| return new DefaultDocumentAdapter(); |
| } |
| |
| /** |
| * Creates the viewer's SWT control. The viewer's text widget either is |
| * the control or is a child of the control. |
| * |
| * @param parent the parent of the viewer's control |
| * @param styles the SWT style bits for the viewer's control |
| */ |
| protected void createControl(Composite parent, int styles) { |
| |
| fTextWidget= createTextWidget(parent, styles); |
| |
| // Support scroll page upon MOD1+MouseWheel |
| fTextWidget.addListener(SWT.MouseVerticalWheel, event -> { |
| if (((event.stateMask & SWT.MOD1) == 0)) |
| return; |
| |
| int topIndex= fTextWidget.getTopIndex(); |
| int bottomIndex= JFaceTextUtil.getBottomIndex(fTextWidget); |
| |
| if (event.count > 0) |
| fTextWidget.setTopIndex(2 * topIndex - bottomIndex); |
| else |
| fTextWidget.setTopIndex(bottomIndex); |
| |
| updateViewportListeners(INTERNAL); |
| }); |
| |
| fTextWidget.addDisposeListener( |
| e -> { |
| fDisposedControl= getControl(); |
| handleDispose(); |
| } |
| ); |
| |
| fTextWidget.setFont(parent.getFont()); |
| fTextWidget.setDoubleClickEnabled(true); |
| |
| /* |
| * Disable SWT Shift+TAB traversal in this viewer |
| * 1GIYQ9K: ITPUI:WINNT - StyledText swallows Shift+TAB |
| */ |
| fTextWidget.addTraverseListener(e -> { |
| if ((SWT.SHIFT == e.stateMask) && ('\t' == e.character)) |
| e.doit= !fTextWidget.getEditable(); |
| }); |
| |
| // where does the first line start |
| fTopInset= -fTextWidget.computeTrim(0, 0, 0, 0).y; |
| |
| fVerifyListener.forward(true); |
| fTextWidget.addVerifyListener(fVerifyListener); |
| |
| fTextWidget.addSelectionListener(new SelectionListener() { |
| @Override |
| public void widgetDefaultSelected(SelectionEvent event) { |
| selectionChanged(event.x, event.y - event.x); |
| } |
| @Override |
| public void widgetSelected(SelectionEvent event) { |
| selectionChanged(event.x, event.y - event.x); |
| } |
| }); |
| |
| fCursorListener= new CursorListener(); |
| fCursorListener.install(); |
| |
| initializeViewportUpdate(); |
| } |
| |
| @Override |
| public Control getControl() { |
| return fTextWidget != null ? fTextWidget : fDisposedControl; |
| } |
| |
| @Override |
| public void activatePlugins() { |
| |
| if (fDoubleClickStrategies != null && !fDoubleClickStrategies.isEmpty() && fDoubleClickStrategyConnector == null) { |
| fDoubleClickStrategyConnector= new TextDoubleClickStrategyConnector(); |
| fTextWidget.addWordMovementListener(fDoubleClickStrategyConnector); |
| fTextWidget.addMouseListener(fDoubleClickStrategyConnector); |
| } |
| |
| ensureHoverControlManagerInstalled(); |
| ensureHyperlinkManagerInstalled(); |
| |
| if (fUndoManager != null) { |
| fUndoManager.connect(this); |
| fUndoManager.reset(); |
| } |
| } |
| |
| /** |
| * After this method has been executed the caller knows that any installed text hover has been installed. |
| */ |
| private void ensureHoverControlManagerInstalled() { |
| if (fTextHovers != null && !fTextHovers.isEmpty() && fHoverControlCreator != null && fTextHoverManager == null) { |
| fTextHoverManager= new TextViewerHoverManager(this, fHoverControlCreator); |
| fTextHoverManager.install(this.getTextWidget()); |
| fTextHoverManager.setSizeConstraints(TEXT_HOVER_WIDTH_CHARS, TEXT_HOVER_HEIGHT_CHARS, false, true); |
| fTextHoverManager.setInformationControlReplacer(new StickyHoverManager(this)); |
| } |
| } |
| |
| @Override |
| public void resetPlugins() { |
| if (fUndoManager != null) |
| fUndoManager.reset(); |
| } |
| |
| /** |
| * Frees all resources allocated by this viewer. Internally called when the viewer's |
| * control has been disposed. |
| */ |
| protected void handleDispose() { |
| |
| setDocument(null); |
| |
| if (fPaintManager != null) { |
| fPaintManager.dispose(); |
| fPaintManager= null; |
| } |
| |
| removeViewPortUpdate(); |
| fViewportGuard= null; |
| |
| if (fViewportListeners != null) { |
| fViewportListeners.clear(); |
| fViewportListeners= null; |
| } |
| |
| if (fTextListeners != null) { |
| fTextListeners.clear(); |
| fTextListeners= null; |
| } |
| |
| if (fTextInputListeners != null) { |
| fTextInputListeners.clear(); |
| fTextInputListeners= null; |
| } |
| |
| if (fPostSelectionChangedListeners != null) { |
| fPostSelectionChangedListeners.clear(); |
| fPostSelectionChangedListeners= null; |
| } |
| |
| if (fAutoIndentStrategies != null) { |
| fAutoIndentStrategies.clear(); |
| fAutoIndentStrategies= null; |
| } |
| |
| if (fUndoManager != null) { |
| fUndoManager.disconnect(); |
| fUndoManager= null; |
| } |
| |
| if (fDoubleClickStrategies != null) { |
| fDoubleClickStrategies.clear(); |
| fDoubleClickStrategies= null; |
| } |
| |
| if (fTextHovers != null) { |
| fTextHovers.clear(); |
| fTextHovers= null; |
| } |
| |
| fDoubleClickStrategyConnector= null; |
| |
| if (fTextHoverManager != null) { |
| fTextHoverManager.dispose(); |
| fTextHoverManager= null; |
| } |
| |
| if (fVisibleDocumentListener !=null) { |
| if (fVisibleDocument != null) |
| fVisibleDocument.removeDocumentListener(fVisibleDocumentListener); |
| fVisibleDocumentListener= null; |
| } |
| |
| if (fDocumentAdapter != null) { |
| fDocumentAdapter.setDocument(null); |
| fDocumentAdapter= null; |
| } |
| |
| if (fSlaveDocumentManager != null) { |
| if (fVisibleDocument != null) |
| fSlaveDocumentManager.freeSlaveDocument(fVisibleDocument); |
| fSlaveDocumentManager= null; |
| } |
| |
| if (fCursorListener != null) { |
| fCursorListener.uninstall(); |
| fCursorListener= null; |
| } |
| |
| if (fHyperlinkManager != null) { |
| fHyperlinkManager.uninstall(); |
| fHyperlinkManager= null; |
| } |
| |
| fHyperlinkDetectors= null; |
| fVisibleDocument= null; |
| fDocument= null; |
| fScroller= null; |
| |
| fTextWidget= null; |
| } |
| |
| |
| //---- simple getters and setters |
| |
| @Override |
| public StyledText getTextWidget() { |
| return fTextWidget; |
| } |
| |
| /** |
| * The delay in milliseconds before an empty selection changed event is sent by the cursor |
| * listener. |
| * <p> |
| * Note: The return value is used to initialize the cursor listener. To return a non-constant |
| * value has no effect. |
| * </p> |
| * <p> |
| * This implementation returns {@link OpenStrategy#getPostSelectionDelay()}. |
| * </p> |
| * |
| * @return delay in milliseconds |
| * @see org.eclipse.jface.util.OpenStrategy |
| * @since 3.0 |
| */ |
| protected int getEmptySelectionChangedEventDelay() { |
| return OpenStrategy.getPostSelectionDelay(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @deprecated since 3.1, use |
| * {@link ITextViewerExtension2#prependAutoEditStrategy(IAutoEditStrategy, String)} and |
| * {@link ITextViewerExtension2#removeAutoEditStrategy(IAutoEditStrategy, String)} instead |
| */ |
| @Deprecated |
| @Override |
| public void setAutoIndentStrategy(IAutoIndentStrategy strategy, String contentType) { |
| setAutoEditStrategies(new IAutoEditStrategy[] { strategy }, contentType); |
| } |
| |
| /** |
| * Sets the given edit strategy as the only strategy for the given content type. |
| * |
| * @param strategies the auto edit strategies |
| * @param contentType the content type |
| * @since 3.1 |
| */ |
| protected final void setAutoEditStrategies(IAutoEditStrategy[] strategies, String contentType) { |
| if (fAutoIndentStrategies == null) |
| fAutoIndentStrategies= new HashMap<>(); |
| |
| List<IAutoEditStrategy> autoEditStrategies= fAutoIndentStrategies.get(contentType); |
| |
| if (strategies == null) { |
| if (autoEditStrategies == null) |
| return; |
| |
| fAutoIndentStrategies.put(contentType, null); |
| |
| } else { |
| if (autoEditStrategies == null) { |
| autoEditStrategies= new ArrayList<>(); |
| fAutoIndentStrategies.put(contentType, autoEditStrategies); |
| } |
| |
| autoEditStrategies.clear(); |
| autoEditStrategies.addAll(Arrays.asList(strategies)); |
| } |
| } |
| |
| @Override |
| public void prependAutoEditStrategy(IAutoEditStrategy strategy, String contentType) { |
| |
| if (strategy == null || contentType == null) |
| throw new IllegalArgumentException(); |
| |
| if (fAutoIndentStrategies == null) |
| fAutoIndentStrategies= new HashMap<>(); |
| |
| List<IAutoEditStrategy> autoEditStrategies= fAutoIndentStrategies.get(contentType); |
| if (autoEditStrategies == null) { |
| autoEditStrategies= new ArrayList<>(); |
| fAutoIndentStrategies.put(contentType, autoEditStrategies); |
| } |
| |
| autoEditStrategies.add(0, strategy); |
| } |
| |
| @Override |
| public void removeAutoEditStrategy(IAutoEditStrategy strategy, String contentType) { |
| if (fAutoIndentStrategies == null) |
| return; |
| |
| List<IAutoEditStrategy> autoEditStrategies= fAutoIndentStrategies.get(contentType); |
| if (autoEditStrategies == null) |
| return; |
| |
| for (final Iterator<IAutoEditStrategy> iterator= autoEditStrategies.iterator(); iterator.hasNext(); ) { |
| if (iterator.next().equals(strategy)) { |
| iterator.remove(); |
| break; |
| } |
| } |
| |
| if (autoEditStrategies.isEmpty()) |
| fAutoIndentStrategies.put(contentType, null); |
| } |
| |
| @Override |
| public void setEventConsumer(IEventConsumer consumer) { |
| fEventConsumer= consumer; |
| } |
| |
| @Override |
| public void setIndentPrefixes(String[] indentPrefixes, String contentType) { |
| |
| int i= -1; |
| boolean ok= (indentPrefixes != null); |
| while (ok && ++i < indentPrefixes.length) |
| ok= (indentPrefixes[i] != null); |
| |
| if (ok) { |
| |
| if (fIndentChars == null) |
| fIndentChars= new HashMap<>(); |
| |
| fIndentChars.put(contentType, indentPrefixes); |
| |
| } else if (fIndentChars != null) |
| fIndentChars.remove(contentType); |
| } |
| |
| @Override |
| public int getTopInset() { |
| return fTopInset; |
| } |
| |
| @Override |
| public boolean isEditable() { |
| if (fTextWidget == null) |
| return false; |
| return fTextWidget.getEditable(); |
| } |
| |
| @Override |
| public void setEditable(boolean editable) { |
| if (fTextWidget != null) |
| fTextWidget.setEditable(editable); |
| } |
| |
| /* |
| * @see ITextViewer#setDefaultPrefixes |
| * @since 2.0 |
| */ |
| @Override |
| public void setDefaultPrefixes(String[] defaultPrefixes, String contentType) { |
| |
| if (defaultPrefixes != null && defaultPrefixes.length > 0) { |
| if (fDefaultPrefixChars == null) |
| fDefaultPrefixChars= new HashMap<>(); |
| fDefaultPrefixChars.put(contentType, defaultPrefixes); |
| } else if (fDefaultPrefixChars != null) |
| fDefaultPrefixChars.remove(contentType); |
| } |
| |
| @Override |
| public void setUndoManager(IUndoManager undoManager) { |
| fUndoManager= undoManager; |
| } |
| |
| @Override |
| public IUndoManager getUndoManager() { |
| return fUndoManager; |
| } |
| |
| @Override |
| public void setTextHover(ITextHover hover, String contentType) { |
| setTextHover(hover, contentType, ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK); |
| } |
| |
| @Override |
| public void setTextHover(ITextHover hover, String contentType, int stateMask) { |
| TextHoverKey key= new TextHoverKey(contentType, stateMask); |
| if (hover != null) { |
| if (fTextHovers == null) { |
| fTextHovers= new HashMap<>(); |
| } |
| fTextHovers.put(key, hover); |
| } else if (fTextHovers != null) |
| fTextHovers.remove(key); |
| |
| ensureHoverControlManagerInstalled(); |
| } |
| |
| @Override |
| public void removeTextHovers(String contentType) { |
| if (fTextHovers == null) |
| return; |
| |
| Iterator<TextHoverKey> iter= new HashSet<>(fTextHovers.keySet()).iterator(); |
| while (iter.hasNext()) { |
| TextHoverKey key= iter.next(); |
| if (key.fContentType.equals(contentType)) |
| fTextHovers.remove(key); |
| } |
| } |
| |
| /** |
| * Returns the text hover for a given offset. |
| * |
| * @param offset the offset for which to return the text hover |
| * @return the text hover for the given offset |
| */ |
| protected ITextHover getTextHover(int offset) { |
| return getTextHover(offset, ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK); |
| } |
| |
| /** |
| * Returns the text hover for a given offset and a given state mask. |
| * |
| * @param offset the offset for which to return the text hover |
| * @param stateMask the SWT event state mask |
| * @return the text hover for the given offset and state mask |
| * @since 2.1 |
| */ |
| protected ITextHover getTextHover(int offset, int stateMask) { |
| if (fTextHovers == null) |
| return null; |
| |
| IDocument document= getDocument(); |
| if (document == null) |
| return null; |
| |
| try { |
| TextHoverKey key= new TextHoverKey(TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true), stateMask); |
| Object textHover= fTextHovers.get(key); |
| if (textHover == null) { |
| // Use default text hover |
| key.setStateMask(ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK); |
| textHover= fTextHovers.get(key); |
| } |
| return (ITextHover) textHover; |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.selectContentTypePlugin")); //$NON-NLS-1$ |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the text hovering controller of this viewer. |
| * |
| * @return the text hovering controller of this viewer |
| * @since 2.0 |
| */ |
| protected AbstractInformationControlManager getTextHoveringController() { |
| return fTextHoverManager; |
| } |
| |
| /** |
| * Sets the creator for the hover controls. |
| * |
| * @param creator the hover control creator |
| * @since 2.0 |
| */ |
| public void setHoverControlCreator(IInformationControlCreator creator) { |
| fHoverControlCreator= creator; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @since 3.4 |
| */ |
| @Override |
| public void setHoverEnrichMode(ITextViewerExtension8.EnrichMode mode) { |
| if (fTextHoverManager == null) |
| return; |
| fTextHoverManager.setHoverEnrichMode(mode); |
| } |
| |
| @Override |
| public boolean requestWidgetToken(IWidgetTokenKeeper requester) { |
| if (fTextWidget != null) { |
| if (fWidgetTokenKeeper != null) { |
| if (fWidgetTokenKeeper == requester) |
| return true; |
| if (fWidgetTokenKeeper.requestWidgetToken(this)) { |
| fWidgetTokenKeeper= requester; |
| return true; |
| } |
| } else { |
| fWidgetTokenKeeper= requester; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean requestWidgetToken(IWidgetTokenKeeper requester, int priority) { |
| if (fTextWidget != null) { |
| if (fWidgetTokenKeeper != null) { |
| |
| if (fWidgetTokenKeeper == requester) |
| return true; |
| |
| boolean accepted= false; |
| if (fWidgetTokenKeeper instanceof IWidgetTokenKeeperExtension) { |
| IWidgetTokenKeeperExtension extension= (IWidgetTokenKeeperExtension) fWidgetTokenKeeper; |
| accepted= extension.requestWidgetToken(this, priority); |
| } else { |
| accepted= fWidgetTokenKeeper.requestWidgetToken(this); |
| } |
| |
| if (accepted) { |
| fWidgetTokenKeeper= requester; |
| return true; |
| } |
| |
| } else { |
| fWidgetTokenKeeper= requester; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void releaseWidgetToken(IWidgetTokenKeeper tokenKeeper) { |
| if (fWidgetTokenKeeper == tokenKeeper) |
| fWidgetTokenKeeper= null; |
| } |
| |
| |
| //---- Selection |
| |
| @Override |
| public Point getSelectedRange() { |
| |
| if (!redraws() && fViewerState != null) |
| return fViewerState.getSelection(); |
| |
| if (fTextWidget != null) { |
| Point p= fTextWidget.getSelectionRange(); |
| p= widgetSelection2ModelSelection(p); |
| if (p != null) |
| return p; |
| } |
| |
| return new Point(-1, -1); |
| } |
| |
| @Override |
| public void setSelectedRange(int selectionOffset, int selectionLength) { |
| |
| if (!redraws()) { |
| if (fViewerState != null) |
| fViewerState.updateSelection(selectionOffset, selectionLength); |
| return; |
| } |
| |
| if (fTextWidget == null) |
| return; |
| |
| IRegion widgetSelection= modelRange2ClosestWidgetRange(new Region(selectionOffset, selectionLength)); |
| if (widgetSelection != null) { |
| |
| int[] selectionRange= new int[] { widgetSelection.getOffset(), widgetSelection.getLength() }; |
| validateSelectionRange(selectionRange); |
| if (selectionRange[0] >= 0) { |
| fTextWidget.setSelectionRange(selectionRange[0], selectionRange[1]); |
| selectionChanged(selectionRange[0], selectionRange[1]); |
| } |
| } |
| } |
| |
| /** |
| * Validates and adapts the given selection range if it is not a valid |
| * widget selection. The widget selection is invalid if it starts or ends |
| * inside a multi-character line delimiter. If so, the selection is adapted to |
| * start <b>after</b> the divided line delimiter and to end <b>before</b> |
| * the divided line delimiter. The parameter passed in is changed in-place |
| * when being adapted. An adaptation to <code>[-1, -1]</code> indicates |
| * that the selection range could not be validated. |
| * Subclasses may reimplement this method. |
| * |
| * @param selectionRange selectionRange[0] is the offset, selectionRange[1] |
| * the length of the selection to validate. |
| * @since 2.0 |
| */ |
| protected void validateSelectionRange(int[] selectionRange) { |
| |
| IDocument document= getVisibleDocument(); |
| if (document == null) { |
| selectionRange[0]= -1; |
| selectionRange[1]= -1; |
| return; |
| } |
| |
| int documentLength= document.getLength(); |
| int offset= selectionRange[0]; |
| int length= selectionRange[1]; |
| |
| if (length < 0) { |
| length= - length; |
| offset -= length; |
| } |
| |
| if (offset <0) |
| offset= 0; |
| |
| if (offset > documentLength) |
| offset= documentLength; |
| |
| int delta= (offset + length) - documentLength; |
| if (delta > 0) |
| length -= delta; |
| |
| try { |
| |
| int lineNumber= document.getLineOfOffset(offset); |
| IRegion lineInformation= document.getLineInformation(lineNumber); |
| |
| int lineEnd= lineInformation.getOffset() + lineInformation.getLength(); |
| delta= offset - lineEnd; |
| if (delta > 0) { |
| // in the middle of a multi-character line delimiter |
| offset= lineEnd; |
| length += delta; |
| String delimiter= document.getLineDelimiter(lineNumber); |
| if (delimiter != null) { |
| int delimiterLength= delimiter.length(); |
| offset += delimiterLength; |
| length -= delimiterLength; |
| } |
| } |
| |
| int end= offset + length; |
| lineInformation= document.getLineInformationOfOffset(end); |
| lineEnd= lineInformation.getOffset() + lineInformation.getLength(); |
| delta= end - lineEnd; |
| if (delta > 0) { |
| // in the middle of a multi-character line delimiter |
| length -= delta; |
| } |
| |
| } catch (BadLocationException x) { |
| selectionRange[0]= -1; |
| selectionRange[1]= -1; |
| return; |
| } |
| |
| if (selectionRange[1] < 0) { |
| selectionRange[0]= offset + length; |
| selectionRange[1]= -length; |
| } else { |
| selectionRange[0]= offset; |
| selectionRange[1]= length; |
| } |
| } |
| |
| @Override |
| public void setSelection(ISelection selection, boolean reveal) { |
| if (selection instanceof IBlockTextSelection && getTextWidget().getBlockSelection()) { |
| IBlockTextSelection s= (IBlockTextSelection) selection; |
| |
| try { |
| int startLine= s.getStartLine(); |
| int endLine= s.getEndLine(); |
| IRegion startLineInfo= fDocument.getLineInformation(startLine); |
| int startLineLength= startLineInfo.getLength(); |
| int startVirtuals= Math.max(0, s.getStartColumn() - startLineLength); |
| |
| IRegion endLineInfo= fDocument.getLineInformation(endLine); |
| int endLineLength= endLineInfo.getLength(); |
| int endVirtuals= Math.max(0, s.getEndColumn() - endLineLength); |
| |
| IRegion startRegion= new Region(startLineInfo.getOffset() + s.getStartColumn() - startVirtuals, 0); |
| int startOffset= modelRange2ClosestWidgetRange(startRegion).getOffset(); |
| IRegion endRegion= new Region(endLineInfo.getOffset() + s.getEndColumn() - endVirtuals, 0); |
| int endOffset= modelRange2ClosestWidgetRange(endRegion).getOffset(); |
| Point clientAreaOrigin= new Point(fTextWidget.getHorizontalPixel(), fTextWidget.getTopPixel()); |
| Point startLocation= Geometry.add(clientAreaOrigin, fTextWidget.getLocationAtOffset(startOffset)); |
| int averageCharWidth= getAverageCharWidth(); |
| startLocation.x += startVirtuals * averageCharWidth; |
| Point endLocation= Geometry.add(clientAreaOrigin, fTextWidget.getLocationAtOffset(endOffset)); |
| endLocation.x += endVirtuals * averageCharWidth; |
| endLocation.y += fTextWidget.getLineHeight(endOffset); |
| |
| int widgetLength= endOffset - startOffset; |
| int[] widgetSelection= { startOffset, widgetLength}; |
| validateSelectionRange(widgetSelection); |
| if (widgetSelection[0] >= 0) { |
| fTextWidget.setBlockSelectionBounds(Geometry.createRectangle(startLocation, Geometry.subtract(endLocation, startLocation))); |
| selectionChanged(startOffset, widgetLength); |
| } |
| } catch (BadLocationException e) { |
| // fall back to linear selection mode |
| setSelectedRange(s.getOffset(), s.getLength()); |
| } |
| if (reveal) |
| revealRange(s.getOffset(), s.getLength()); |
| } else if (selection instanceof ITextSelection) { |
| ITextSelection s= (ITextSelection) selection; |
| setSelectedRange(s.getOffset(), s.getLength()); |
| if (reveal) |
| revealRange(s.getOffset(), s.getLength()); |
| } |
| } |
| |
| @Override |
| public ISelection getSelection() { |
| if (fTextWidget != null && fTextWidget.getBlockSelection()) { |
| int[] ranges= fTextWidget.getSelectionRanges(); |
| int startOffset= ranges[0]; |
| int endOffset= ranges[ranges.length - 2] + ranges[ranges.length - 1]; |
| |
| // getBlockSelectionBounds returns pixel coordinates relative to document |
| Rectangle bounds= fTextWidget.getBlockSelectionBounds(); |
| int clientAreaX= fTextWidget.getHorizontalPixel(); |
| int startX= bounds.x - clientAreaX; |
| int endX= bounds.x + bounds.width - clientAreaX; |
| int avgCharWidth= getAverageCharWidth(); |
| int startVirtuals= computeVirtualChars(startOffset, startX, avgCharWidth); |
| int endVirtuals= computeVirtualChars(endOffset, endX, avgCharWidth); |
| |
| IDocument document= getDocument(); |
| Point modelSelection= widgetSelection2ModelSelection(new Point(startOffset, endOffset - startOffset)); |
| if (modelSelection == null) |
| return TextSelection.emptySelection(); |
| startOffset= modelSelection.x; |
| endOffset= modelSelection.x + modelSelection.y; |
| |
| try { |
| int startLine= document.getLineOfOffset(startOffset); |
| int endLine= document.getLineOfOffset(endOffset); |
| |
| int startColumn= startOffset - document.getLineOffset(startLine) + startVirtuals; |
| int endColumn= endOffset - document.getLineOffset(endLine) + endVirtuals; |
| if (startLine == -1 || endLine == -1) |
| return TextSelection.emptySelection(); |
| return new BlockTextSelection(document, startLine, startColumn, endLine, endColumn, fTextWidget.getTabs()); |
| } catch (BadLocationException e) { |
| return TextSelection.emptySelection(); |
| } |
| } |
| |
| Point p= getSelectedRange(); |
| if (p.x == -1 || p.y == -1) |
| return TextSelection.emptySelection(); |
| |
| return new TextSelection(getDocument(), p.x, p.y); |
| } |
| |
| /** |
| * Returns the number of virtual characters that exist beyond the end-of-line at offset |
| * <code>offset</code> for an x-coordinate <code>x</code>. |
| * |
| * @param offset the non-virtual offset to consider |
| * @param x the x-coordinate (relative to the client area) of the possibly virtual offset |
| * @param avgCharWidth the average character width to assume for virtual spaces |
| * @return the number of virtual spaces needed to reach <code>x</code> from the location of |
| * <code>offset</code>, <code>0</code> if <code>x</code> points inside the text |
| * @since 3.5 |
| */ |
| private int computeVirtualChars(int offset, int x, int avgCharWidth) { |
| int diff= x - fTextWidget.getLocationAtOffset(offset).x; |
| return diff > 0 ? diff / avgCharWidth : 0; |
| } |
| |
| @Override |
| public ISelectionProvider getSelectionProvider() { |
| return this; |
| } |
| |
| @Override |
| public void addPostSelectionChangedListener(ISelectionChangedListener listener) { |
| |
| Assert.isNotNull(listener); |
| |
| if (fPostSelectionChangedListeners == null) |
| fPostSelectionChangedListeners= new ArrayList<>(); |
| |
| if (!fPostSelectionChangedListeners.contains(listener)) |
| fPostSelectionChangedListeners.add(listener); |
| } |
| |
| @Override |
| public void removePostSelectionChangedListener(ISelectionChangedListener listener) { |
| |
| Assert.isNotNull(listener); |
| |
| if (fPostSelectionChangedListeners != null) { |
| fPostSelectionChangedListeners.remove(listener); |
| if (fPostSelectionChangedListeners.size() == 0) |
| fPostSelectionChangedListeners= null; |
| } |
| } |
| |
| /** |
| * Get the text widget's display. |
| * |
| * @return the display or <code>null</code> if the display cannot be retrieved or if the display is disposed |
| * @since 3.0 |
| */ |
| private Display getDisplay() { |
| if (fTextWidget == null || fTextWidget.isDisposed()) |
| return null; |
| |
| Display display= fTextWidget.getDisplay(); |
| if (display != null && display.isDisposed()) |
| return null; |
| |
| return display; |
| } |
| |
| /** |
| * Starts a timer to send out a post selection changed event. |
| * |
| * @param fireEqualSelection <code>true</code> iff the event must be fired if the selection does not change |
| * @since 3.0 |
| */ |
| private void queuePostSelectionChanged(final boolean fireEqualSelection) { |
| Display display= getDisplay(); |
| if (display == null) |
| return; |
| |
| fNumberOfPostSelectionChangedEvents[0]++; |
| fFireEqualPostSelectionChange|= fireEqualSelection; |
| display.timerExec(getEmptySelectionChangedEventDelay(), new Runnable() { |
| final int id= fNumberOfPostSelectionChangedEvents[0]; |
| @Override |
| public void run() { |
| if (id == fNumberOfPostSelectionChangedEvents[0]) { |
| // Check again because this is executed after the delay |
| if (getDisplay() != null) { |
| Point selection= fTextWidget.getSelectionRange(); |
| if (selection != null) { |
| IRegion r= widgetRange2ModelRange(new Region(selection.x, selection.y)); |
| if (fFireEqualPostSelectionChange || (r != null && !r.equals(fLastSentPostSelectionChange)) || r == null) { |
| fLastSentPostSelectionChange= r; |
| fFireEqualPostSelectionChange= false; |
| firePostSelectionChanged(selection.x, selection.y); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Sends out a text selection changed event to all registered post selection changed listeners. |
| * |
| * @param offset the offset of the newly selected range in the visible document |
| * @param length the length of the newly selected range in the visible document |
| * @since 3.0 |
| */ |
| protected void firePostSelectionChanged(int offset, int length) { |
| if (redraws()) { |
| IRegion r= widgetRange2ModelRange(new Region(offset, length)); |
| ISelection selection= r != null ? new TextSelection(getDocument(), r.getOffset(), r.getLength()) : TextSelection.emptySelection(); |
| SelectionChangedEvent event= new SelectionChangedEvent(this, selection); |
| firePostSelectionChanged(event); |
| } |
| } |
| |
| /** |
| * Sends out a text selection changed event to all registered listeners and |
| * registers the selection changed event to be sent out to all post selection |
| * listeners. |
| * |
| * @param offset the offset of the newly selected range in the visible document |
| * @param length the length of the newly selected range in the visible document |
| */ |
| protected void selectionChanged(int offset, int length) { |
| queuePostSelectionChanged(true); |
| fireSelectionChanged(offset, length); |
| } |
| |
| /** |
| * Sends out a text selection changed event to all registered listeners. |
| * |
| * @param offset the offset of the newly selected range in the visible document |
| * @param length the length of the newly selected range in the visible document |
| * @since 3.0 |
| */ |
| protected void fireSelectionChanged(int offset, int length) { |
| if (redraws()) { |
| if (length < 0) { |
| length= -length; |
| offset= offset + length; |
| } |
| IRegion r= widgetRange2ModelRange(new Region(offset, length)); |
| if ((r != null && !r.equals(fLastSentSelectionChange)) || r == null) { |
| fLastSentSelectionChange= r; |
| ISelection selection= r != null ? new TextSelection(getDocument(), r.getOffset(), r.getLength()) : TextSelection.emptySelection(); |
| SelectionChangedEvent event= new SelectionChangedEvent(this, selection); |
| fireSelectionChanged(event); |
| } |
| } |
| } |
| |
| /** |
| * Sends the given event to all registered post selection changed listeners. |
| * |
| * @param event the selection event |
| * @since 3.0 |
| */ |
| private void firePostSelectionChanged(SelectionChangedEvent event) { |
| List<ISelectionChangedListener> listeners= fPostSelectionChangedListeners; |
| if (listeners != null) { |
| listeners= new ArrayList<>(listeners); |
| for (int i= 0; i < listeners.size(); i++) { |
| ISelectionChangedListener l= listeners.get(i); |
| l.selectionChanged(event); |
| } |
| } |
| } |
| |
| /** |
| * Sends out a mark selection changed event to all registered listeners. |
| * |
| * @param offset the offset of the mark selection in the visible document, the offset is <code>-1</code> if the mark was cleared |
| * @param length the length of the mark selection, may be negative if the caret is before the mark. |
| * @since 2.0 |
| */ |
| protected void markChanged(int offset, int length) { |
| if (redraws()) { |
| |
| if (offset != -1) { |
| IRegion r= widgetRange2ModelRange(new Region(offset, length)); |
| offset= r.getOffset(); |
| length= r.getLength(); |
| } |
| |
| ISelection selection= new MarkSelection(getDocument(), offset, length); |
| SelectionChangedEvent event= new SelectionChangedEvent(this, selection); |
| fireSelectionChanged(event); |
| } |
| } |
| |
| |
| //---- Text listeners |
| |
| @Override |
| public void addTextListener(ITextListener listener) { |
| |
| Assert.isNotNull(listener); |
| |
| if (fTextListeners == null) |
| fTextListeners= new ArrayList<>(); |
| |
| if (!fTextListeners.contains(listener)) |
| fTextListeners.add(listener); |
| } |
| |
| @Override |
| public void removeTextListener(ITextListener listener) { |
| |
| Assert.isNotNull(listener); |
| |
| if (fTextListeners != null) { |
| fTextListeners.remove(listener); |
| if (fTextListeners.size() == 0) |
| fTextListeners= null; |
| } |
| } |
| |
| /** |
| * Informs all registered text listeners about the change specified by the |
| * widget command. This method does not use a robust iterator. |
| * |
| * @param cmd the widget command translated into a text event sent to all text listeners |
| */ |
| protected void updateTextListeners(WidgetCommand cmd) { |
| List<ITextListener> textListeners= fTextListeners; |
| if (textListeners != null) { |
| textListeners= new ArrayList<>(textListeners); |
| DocumentEvent event= cmd.event; |
| if (event instanceof SlaveDocumentEvent) |
| event= ((SlaveDocumentEvent) event).getMasterEvent(); |
| |
| TextEvent e= new TextEvent(cmd.start, cmd.length, cmd.text, cmd.preservedText, event, redraws()); |
| for (int i= 0; i < textListeners.size(); i++) { |
| ITextListener l= textListeners.get(i); |
| l.textChanged(e); |
| } |
| } |
| } |
| |
| //---- Text input listeners |
| |
| @Override |
| public void addTextInputListener(ITextInputListener listener) { |
| |
| Assert.isNotNull(listener); |
| |
| if (fTextInputListeners == null) |
| fTextInputListeners= new ArrayList<>(); |
| |
| if (!fTextInputListeners.contains(listener)) |
| fTextInputListeners.add(listener); |
| } |
| |
| @Override |
| public void removeTextInputListener(ITextInputListener listener) { |
| |
| Assert.isNotNull(listener); |
| |
| if (fTextInputListeners != null) { |
| fTextInputListeners.remove(listener); |
| if (fTextInputListeners.size() == 0) |
| fTextInputListeners= null; |
| } |
| } |
| |
| /** |
| * Informs all registered text input listeners about the forthcoming input change, |
| * This method does not use a robust iterator. |
| * |
| * @param oldInput the old input document |
| * @param newInput the new input document |
| */ |
| protected void fireInputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { |
| List<ITextInputListener> listener= fTextInputListeners; |
| if (listener != null) { |
| for (int i= 0; i < listener.size(); i++) { |
| ITextInputListener l= listener.get(i); |
| l.inputDocumentAboutToBeChanged(oldInput, newInput); |
| } |
| } |
| } |
| |
| /** |
| * Informs all registered text input listeners about the successful input change, |
| * This method does not use a robust iterator. |
| * |
| * @param oldInput the old input document |
| * @param newInput the new input document |
| */ |
| protected void fireInputDocumentChanged(IDocument oldInput, IDocument newInput) { |
| List<ITextInputListener> listener= fTextInputListeners; |
| if (listener != null) { |
| for (int i= 0; i < listener.size(); i++) { |
| ITextInputListener l= listener.get(i); |
| l.inputDocumentChanged(oldInput, newInput); |
| } |
| } |
| } |
| |
| //---- Document |
| |
| @Override |
| public Object getInput() { |
| return getDocument(); |
| } |
| |
| @Override |
| public IDocument getDocument() { |
| return fDocument; |
| } |
| |
| @Override |
| public void setInput(Object input) { |
| |
| IDocument document= null; |
| if (input instanceof IDocument) |
| document= (IDocument) input; |
| |
| setDocument(document); |
| } |
| |
| @Override |
| public void setDocument(IDocument document) { |
| |
| fReplaceTextPresentation= true; |
| fireInputDocumentAboutToBeChanged(fDocument, document); |
| |
| IDocument oldDocument= fDocument; |
| fDocument= document; |
| |
| setVisibleDocument(fDocument); |
| |
| resetPlugins(); |
| inputChanged(fDocument, oldDocument); |
| |
| fireInputDocumentChanged(oldDocument, fDocument); |
| fLastSentSelectionChange= null; |
| fReplaceTextPresentation= false; |
| } |
| |
| @Override |
| public void setDocument(IDocument document, int modelRangeOffset, int modelRangeLength) { |
| |
| fReplaceTextPresentation= true; |
| fireInputDocumentAboutToBeChanged(fDocument, document); |
| |
| IDocument oldDocument= fDocument; |
| fDocument= document; |
| |
| try { |
| |
| IDocument slaveDocument= createSlaveDocument(document); |
| updateSlaveDocument(slaveDocument, modelRangeOffset, modelRangeLength); |
| setVisibleDocument(slaveDocument); |
| |
| } catch (BadLocationException x) { |
| throw new IllegalArgumentException(JFaceTextMessages.getString("TextViewer.error.invalid_visible_region_1")); //$NON-NLS-1$ |
| } |
| |
| resetPlugins(); |
| inputChanged(fDocument, oldDocument); |
| |
| fireInputDocumentChanged(oldDocument, fDocument); |
| fLastSentSelectionChange= null; |
| fReplaceTextPresentation= false; |
| } |
| |
| /** |
| * Creates a slave document for the given document if there is a slave document manager |
| * associated with this viewer. |
| * |
| * @param document the master document |
| * @return the newly created slave document |
| * @since 2.1 |
| */ |
| protected IDocument createSlaveDocument(IDocument document) { |
| ISlaveDocumentManager manager= getSlaveDocumentManager(); |
| if (manager != null) { |
| if (manager.isSlaveDocument(document)) |
| return document; |
| return manager.createSlaveDocument(document); |
| } |
| return document; |
| } |
| |
| /** |
| * Sets the given slave document to the specified range of its master document. |
| * |
| * @param visibleDocument the slave document |
| * @param visibleRegionOffset the offset of the master document range |
| * @param visibleRegionLength the length of the master document range |
| * @return <code>true</code> if the slave has been adapted successfully |
| * @throws BadLocationException in case the specified range is not valid in the master document |
| * @since 2.1 |
| * @deprecated use <code>updateSlaveDocument</code> instead |
| */ |
| @Deprecated |
| protected boolean updateVisibleDocument(IDocument visibleDocument, int visibleRegionOffset, int visibleRegionLength) throws BadLocationException { |
| if (visibleDocument instanceof ChildDocument) { |
| ChildDocument childDocument= (ChildDocument) visibleDocument; |
| |
| IDocument document= childDocument.getParentDocument(); |
| int line= document.getLineOfOffset(visibleRegionOffset); |
| int offset= document.getLineOffset(line); |
| int length= (visibleRegionOffset - offset) + visibleRegionLength; |
| |
| // Bug 465684: It is not possible to determine the difference between a |
| // set parent document range of 0, 0 vs an unset one. So in the 0, 0 case |
| // always set the parent document range. |
| Position parentRange= childDocument.getParentDocumentRange(); |
| if (offset != parentRange.getOffset() || length != parentRange.getLength() |
| || (offset == 0 && length == 0)) { |
| childDocument.setParentDocumentRange(offset, length); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Updates the given slave document to show the specified range of its master document. |
| * |
| * @param slaveDocument the slave document |
| * @param modelRangeOffset the offset of the master document range |
| * @param modelRangeLength the length of the master document range |
| * @return <code>true</code> if the slave has been adapted successfully |
| * @throws BadLocationException in case the specified range is not valid in the master document |
| * @since 3.0 |
| */ |
| protected boolean updateSlaveDocument(IDocument slaveDocument, int modelRangeOffset, int modelRangeLength) throws BadLocationException { |
| return updateVisibleDocument(slaveDocument, modelRangeOffset, modelRangeLength); |
| } |
| |
| |
| |
| //---- View ports |
| |
| /** |
| * Initializes all listeners and structures required to set up view port listeners. |
| */ |
| private void initializeViewportUpdate() { |
| |
| if (fViewportGuard != null) |
| return; |
| |
| if (fTextWidget != null) { |
| |
| fViewportGuard= new ViewportGuard(); |
| fLastTopPixel= -1; |
| |
| fTextWidget.addKeyListener(fViewportGuard); |
| fTextWidget.addMouseListener(fViewportGuard); |
| |
| fScroller= fTextWidget.getVerticalBar(); |
| if (fScroller != null) |
| fScroller.addSelectionListener(fViewportGuard); |
| } |
| } |
| |
| /** |
| * Removes all listeners and structures required to set up view port listeners. |
| */ |
| private void removeViewPortUpdate() { |
| |
| if (fTextWidget != null) { |
| |
| fTextWidget.removeKeyListener(fViewportGuard); |
| fTextWidget.removeMouseListener(fViewportGuard); |
| |
| if (fScroller != null && !fScroller.isDisposed()) { |
| fScroller.removeSelectionListener(fViewportGuard); |
| fScroller= null; |
| } |
| |
| fViewportGuard= null; |
| } |
| } |
| |
| @Override |
| public void addViewportListener(IViewportListener listener) { |
| |
| if (fViewportListeners == null) { |
| fViewportListeners= new ArrayList<>(); |
| initializeViewportUpdate(); |
| } |
| |
| if (!fViewportListeners.contains(listener)) |
| fViewportListeners.add(listener); |
| } |
| |
| @Override |
| public void removeViewportListener(IViewportListener listener) { |
| if (fViewportListeners != null) |
| fViewportListeners.remove(listener); |
| } |
| |
| /** |
| * Checks whether the view port changed and if so informs all registered |
| * listeners about the change. |
| * |
| * @param origin describes under which circumstances this method has been called. |
| * |
| * @see IViewportListener |
| */ |
| protected void updateViewportListeners(int origin) { |
| |
| if (redraws()) { |
| int topPixel= fTextWidget.getTopPixel(); |
| if (topPixel >= 0 && topPixel != fLastTopPixel) { |
| if (fViewportListeners != null) { |
| for (int i= 0; i < fViewportListeners.size(); i++) { |
| IViewportListener l= fViewportListeners.get(i); |
| l.viewportChanged(topPixel); |
| } |
| } |
| fLastTopPixel= topPixel; |
| } |
| } |
| } |
| |
| //---- scrolling and revealing |
| |
| @Override |
| public int getTopIndex() { |
| |
| if (fTextWidget != null) { |
| int top= fTextWidget.getTopIndex(); |
| return widgetLine2ModelLine(top); |
| } |
| |
| return -1; |
| } |
| |
| @Override |
| public void setTopIndex(int index) { |
| |
| if (fTextWidget != null) { |
| |
| int widgetLine= modelLine2WidgetLine(index); |
| if (widgetLine == -1) |
| widgetLine= getClosestWidgetLineForModelLine(index); |
| |
| if (widgetLine > -1) { |
| fTextWidget.setTopIndex(widgetLine); |
| updateViewportListeners(INTERNAL); |
| } |
| } |
| } |
| |
| /** |
| * Returns the number of lines that can fully fit into the viewport. This is computed by |
| * dividing the widget's client area height by the widget's line height. The result is only |
| * accurate if the widget does not use variable line heights - for that reason, clients should |
| * not use this method any longer and use the client area height of the text widget to find out |
| * how much content fits into it. |
| * |
| * @return the view port height in lines |
| * @deprecated as of 3.2 |
| */ |
| @Deprecated |
| protected int getVisibleLinesInViewport() { |
| if (fTextWidget != null) { |
| Rectangle clArea= fTextWidget.getClientArea(); |
| if (!clArea.isEmpty()) |
| return clArea.height / fTextWidget.getLineHeight(); |
| } |
| return -1; |
| } |
| |
| @Override |
| public int getBottomIndex() { |
| |
| if (fTextWidget == null) |
| return -1; |
| |
| int widgetBottom= JFaceTextUtil.getBottomIndex(fTextWidget); |
| return widgetLine2ModelLine(widgetBottom); |
| } |
| |
| @Override |
| public int getTopIndexStartOffset() { |
| |
| if (fTextWidget != null) { |
| int top= fTextWidget.getTopIndex(); |
| try { |
| top= getVisibleDocument().getLineOffset(top); |
| return widgetOffset2ModelOffset(top); |
| } catch (BadLocationException ex) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.getTopIndexStartOffset")); //$NON-NLS-1$ |
| } |
| } |
| |
| return -1; |
| } |
| |
| @Override |
| public int getBottomIndexEndOffset() { |
| try { |
| |
| IRegion line= getDocument().getLineInformation(getBottomIndex()); |
| int bottomEndOffset= line.getOffset() + line.getLength() - 1; |
| |
| IRegion coverage= getModelCoverage(); |
| if (coverage == null) |
| return -1; |
| |
| int coverageEndOffset= coverage.getOffset() + coverage.getLength() - 1; |
| return Math.min(coverageEndOffset, bottomEndOffset); |
| |
| } catch (BadLocationException ex) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.getBottomIndexEndOffset")); //$NON-NLS-1$ |
| return getDocument().getLength() - 1; |
| } |
| } |
| |
| @Override |
| public void revealRange(int start, int length) { |
| |
| if (fTextWidget == null || !redraws()) |
| return; |
| |
| IRegion modelRange= new Region(start, length); |
| IRegion widgetRange= modelRange2ClosestWidgetRange(modelRange); |
| if (widgetRange != null) { |
| |
| int[] range= new int[] { widgetRange.getOffset(), widgetRange.getLength() }; |
| validateSelectionRange(range); |
| if (range[0] >= 0) |
| internalRevealRangeWithWorkaround(range[0], range[0] + range[1]); |
| |
| } else { |
| |
| IRegion coverage= getModelCoverage(); |
| int cursor= (coverage == null || start < coverage.getOffset()) ? 0 : getVisibleDocument().getLength(); |
| internalRevealRangeWithWorkaround(cursor, cursor); |
| } |
| } |
| |
| /** |
| * First makes sure that the layout is not deferred (workaround for Platform UI bug 375576) and |
| * then reveals the given range of the visible document and. |
| * <p> |
| * NOTE: Only {@link #revealRange(int, int)} needs to use this method. The other methods are |
| * called at a time where the editor is already realized. |
| * </p> |
| * |
| * @param start the start offset of the range |
| * @param end the end offset of the range |
| * @since 3.8.1, but only used/effective in 4.x |
| */ |
| private void internalRevealRangeWithWorkaround(int start, int end) { |
| |
| // XXX: Workaround for https://bugs.eclipse.org/375576 |
| final Shell shell= fTextWidget.getShell(); // only the shell layout is deferred |
| int d= 0; |
| for (; shell.isLayoutDeferred(); d++) |
| shell.setLayoutDeferred(false); |
| try { |
| internalRevealRange(start, end); |
| } finally { |
| for (; d > 0; d--) |
| shell.setLayoutDeferred(true); |
| } |
| |
| } |
| |
| /** |
| * Reveals the given range of the visible document. |
| * |
| * @param start the start offset of the range |
| * @param end the end offset of the range |
| */ |
| protected void internalRevealRange(int start, int end) { |
| |
| try { |
| |
| IDocument doc= getVisibleDocument(); |
| |
| int startLine= doc.getLineOfOffset(start); |
| int endLine= doc.getLineOfOffset(end); |
| |
| int top= fTextWidget.getTopIndex(); |
| if (top > -1) { |
| |
| // scroll vertically |
| int bottom= JFaceTextUtil.getBottomIndex(fTextWidget); |
| int lines= bottom - top; |
| |
| // if the widget is not scrollable as it is displaying the entire content |
| // setTopIndex won't have any effect. |
| |
| if (startLine >= top && startLine <= bottom && endLine >= top && endLine <= bottom ) { |
| |
| // do not scroll at all as it is already visible |
| |
| } else { |
| |
| int delta= Math.max(0, lines - (endLine - startLine)); |
| fTextWidget.setTopIndex(startLine - delta/3); |
| updateViewportListeners(INTERNAL); |
| } |
| |
| // scroll horizontally |
| |
| if (endLine < startLine) { |
| endLine += startLine; |
| startLine= endLine - startLine; |
| endLine -= startLine; |
| } |
| |
| int startPixel= -1; |
| int endPixel= -1; |
| |
| if (endLine > startLine) { |
| // reveal the beginning of the range in the start line |
| IRegion extent= getExtent(start, start); |
| startPixel= extent.getOffset() + fTextWidget.getHorizontalPixel(); |
| endPixel= startPixel; |
| |
| } else { |
| IRegion extent= getExtent(start, end); |
| startPixel= extent.getOffset() + fTextWidget.getHorizontalPixel(); |
| endPixel= startPixel + extent.getLength(); |
| } |
| |
| int visibleStart= fTextWidget.getHorizontalPixel(); |
| int visibleEnd= visibleStart + fTextWidget.getClientArea().width; |
| |
| // scroll only if not yet visible |
| if (startPixel < visibleStart || visibleEnd < endPixel) { |
| |
| // set buffer zone to 10 pixels |
| int bufferZone= 10; |
| |
| int newOffset= visibleStart; |
| |
| int visibleWidth= visibleEnd - visibleStart; |
| int selectionPixelWidth= endPixel - startPixel; |
| |
| if (startPixel < visibleStart) |
| newOffset= startPixel; |
| else if (selectionPixelWidth + bufferZone < visibleWidth) |
| newOffset= endPixel + bufferZone - visibleWidth; |
| else |
| newOffset= startPixel; |
| |
| float index= ((float)newOffset) / ((float)getAverageCharWidth()); |
| |
| fTextWidget.setHorizontalIndex(Math.round(index)); |
| } |
| |
| } |
| } catch (BadLocationException e) { |
| throw new IllegalArgumentException(JFaceTextMessages.getString("TextViewer.error.invalid_range")); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Returns the width of the text when being drawn into this viewer's widget. |
| * |
| * @param text the string to measure |
| * @return the width of the presentation of the given string |
| * @deprecated use <code>getWidthInPixels(int, int)</code> instead |
| */ |
| @Deprecated |
| final protected int getWidthInPixels(String text) { |
| GC gc= new GC(fTextWidget); |
| gc.setFont(fTextWidget.getFont()); |
| Point extent= gc.textExtent(text); |
| gc.dispose(); |
| return extent.x; |
| } |
| |
| /** |
| * Returns the region covered by the given start and end offset. |
| * The result is relative to the upper left corner of the widget |
| * client area. |
| * |
| * @param start offset relative to the start of this viewer's view port |
| * 0 <= offset <= getCharCount() |
| * @param end offset relative to the start of this viewer's view port |
| * 0 <= offset <= getCharCount() |
| * @return the region covered by start and end offset |
| */ |
| final protected IRegion getExtent(int start, int end) { |
| if (end > 0 && start < end) { |
| Rectangle bounds= fTextWidget.getTextBounds(start, end - 1); |
| return new Region(bounds.x, bounds.width); |
| } |
| |
| return new Region(fTextWidget.getLocationAtOffset(start).x, 0); |
| } |
| |
| /** |
| * Returns the width of the representation of a text range in the |
| * visible region of the viewer's document as drawn in this viewer's |
| * widget. |
| * |
| * @param offset the offset of the text range in the visible region |
| * @param length the length of the text range in the visible region |
| * @return the width of the presentation of the specified text range |
| * @since 2.0 |
| */ |
| final protected int getWidthInPixels(int offset, int length) { |
| return getExtent(offset, offset + length).getLength(); |
| } |
| |
| /** |
| * Returns the average character width of this viewer's widget. |
| * |
| * @return the average character width of this viewer's widget |
| */ |
| final protected int getAverageCharWidth() { |
| return JFaceTextUtil.getAverageCharWidth(getTextWidget()); |
| } |
| |
| @Override |
| public void refresh() { |
| setDocument(getDocument()); |
| } |
| |
| //---- visible range support |
| |
| /** |
| * Returns the slave document manager |
| * |
| * @return the slave document manager |
| * @since 2.1 |
| */ |
| protected ISlaveDocumentManager getSlaveDocumentManager() { |
| if (fSlaveDocumentManager == null) |
| fSlaveDocumentManager= createSlaveDocumentManager(); |
| return fSlaveDocumentManager; |
| } |
| |
| /** |
| * Creates a new slave document manager. This implementation always |
| * returns a <code>ChildDocumentManager</code>. |
| * |
| * @return ISlaveDocumentManager |
| * @since 2.1 |
| */ |
| protected ISlaveDocumentManager createSlaveDocumentManager() { |
| return new ChildDocumentManager(); |
| } |
| |
| @Override |
| public final void invalidateTextPresentation() { |
| if (fVisibleDocument != null) { |
| fWidgetCommand.event= null; |
| fWidgetCommand.start= 0; |
| fWidgetCommand.length= fVisibleDocument.getLength(); |
| fWidgetCommand.text= fVisibleDocument.get(); |
| updateTextListeners(fWidgetCommand); |
| } |
| } |
| |
| /** |
| * Invalidates the given range of the text presentation. |
| * |
| * @param offset the offset of the range to be invalidated |
| * @param length the length of the range to be invalidated |
| * @since 2.1 |
| */ |
| @Override |
| public final void invalidateTextPresentation(int offset, int length) { |
| if (fVisibleDocument != null) { |
| |
| IRegion widgetRange= modelRange2WidgetRange(new Region(offset, length)); |
| if (widgetRange != null) { |
| |
| fWidgetCommand.event= null; |
| fWidgetCommand.start= widgetRange.getOffset(); |
| fWidgetCommand.length= widgetRange.getLength(); |
| |
| try { |
| fWidgetCommand.text= fVisibleDocument.get(widgetRange.getOffset(), widgetRange.getLength()); |
| updateTextListeners(fWidgetCommand); |
| } catch (BadLocationException x) { |
| // can not happen because of previous checking |
| } |
| } |
| } |
| } |
| |
| /** |
| * Initializes the text widget with the visual document and |
| * invalidates the overall presentation. |
| */ |
| private void initializeWidgetContents() { |
| |
| if (fTextWidget != null && fVisibleDocument != null) { |
| |
| // set widget content |
| if (fDocumentAdapter == null) |
| fDocumentAdapter= createDocumentAdapter(); |
| |
| fDocumentAdapter.setDocument(fVisibleDocument); |
| fTextWidget.setContent(fDocumentAdapter); |
| |
| // invalidate presentation |
| invalidateTextPresentation(); |
| } |
| } |
| |
| /** |
| * Frees the given document if it is a slave document. |
| * |
| * @param slave the potential slave document |
| * @since 3.0 |
| */ |
| protected void freeSlaveDocument(IDocument slave) { |
| ISlaveDocumentManager manager= getSlaveDocumentManager(); |
| if (manager != null && manager.isSlaveDocument(slave)) |
| manager.freeSlaveDocument(slave); |
| } |
| |
| /** |
| * Sets this viewer's visible document. The visible document represents the |
| * visible region of the viewer's input document. |
| * |
| * @param document the visible document |
| */ |
| protected void setVisibleDocument(IDocument document) { |
| |
| if (fVisibleDocument == document && fVisibleDocument instanceof ChildDocument) { |
| // optimization for new child documents |
| return; |
| } |
| |
| if (fVisibleDocument != null) { |
| if (fVisibleDocumentListener != null) |
| fVisibleDocument.removeDocumentListener(fVisibleDocumentListener); |
| if (fVisibleDocument != document) |
| freeSlaveDocument(fVisibleDocument); |
| } |
| |
| fVisibleDocument= document; |
| initializeDocumentInformationMapping(fVisibleDocument); |
| |
| initializeWidgetContents(); |
| |
| fFindReplaceDocumentAdapter= null; |
| if (fVisibleDocument != null && fVisibleDocumentListener != null) |
| fVisibleDocument.addDocumentListener(fVisibleDocumentListener); |
| } |
| |
| /** |
| * Hook method called when the visible document is about to be changed. |
| * <p> |
| * Subclasses may override. |
| * |
| * @param event the document event |
| * @since 3.0 |
| */ |
| protected void handleVisibleDocumentAboutToBeChanged(DocumentEvent event) { |
| } |
| |
| /** |
| * Hook method called when the visible document has been changed. |
| * <p> |
| * Subclasses may override. |
| * |
| * @param event the document event |
| * @since 3.0 |
| */ |
| protected void handleVisibleDocumentChanged(DocumentEvent event) { |
| } |
| |
| /** |
| * Initializes the document information mapping between the given slave document and |
| * its master document. |
| * |
| * @param visibleDocument the slave document |
| * @since 2.1 |
| */ |
| protected void initializeDocumentInformationMapping(IDocument visibleDocument) { |
| ISlaveDocumentManager manager= getSlaveDocumentManager(); |
| fInformationMapping= manager == null ? null : manager.createMasterSlaveMapping(visibleDocument); |
| } |
| |
| /** |
| * Returns the viewer's visible document. |
| * |
| * @return the viewer's visible document |
| */ |
| protected IDocument getVisibleDocument() { |
| return fVisibleDocument; |
| } |
| |
| /** |
| * Returns the offset of the visible region. |
| * |
| * @return the offset of the visible region |
| */ |
| protected int _getVisibleRegionOffset() { |
| |
| IDocument document= getVisibleDocument(); |
| if (document instanceof ChildDocument) { |
| ChildDocument cdoc= (ChildDocument) document; |
| return cdoc.getParentDocumentRange().getOffset(); |
| } |
| |
| return 0; |
| } |
| |
| @Override |
| public IRegion getVisibleRegion() { |
| |
| IDocument document= getVisibleDocument(); |
| if (document instanceof ChildDocument) { |
| Position p= ((ChildDocument) document).getParentDocumentRange(); |
| return new Region(p.getOffset(), p.getLength()); |
| } |
| |
| return new Region(0, document == null ? 0 : document.getLength()); |
| } |
| |
| @Override |
| public boolean overlapsWithVisibleRegion(int start, int length) { |
| IDocument document= getVisibleDocument(); |
| if (document instanceof ChildDocument) { |
| ChildDocument cdoc= (ChildDocument) document; |
| return cdoc.getParentDocumentRange().overlapsWith(start, length); |
| } else if (document != null) { |
| int size= document.getLength(); |
| return (start >= 0 && length >= 0 && start + length <= size); |
| } |
| return false; |
| } |
| |
| @Override |
| public void setVisibleRegion(int start, int length) { |
| |
| IRegion region= getVisibleRegion(); |
| if (start == region.getOffset() && length == region.getLength()) { |
| // nothing to change |
| return; |
| } |
| |
| setRedraw(false); |
| try { |
| |
| IDocument slaveDocument= createSlaveDocument(getVisibleDocument()); |
| if (updateSlaveDocument(slaveDocument, start, length)) |
| setVisibleDocument(slaveDocument); |
| |
| } catch (BadLocationException x) { |
| throw new IllegalArgumentException(JFaceTextMessages.getString("TextViewer.error.invalid_visible_region_2")); //$NON-NLS-1$ |
| } finally { |
| setRedraw(true); |
| } |
| } |
| |
| @Override |
| public void resetVisibleRegion() { |
| ISlaveDocumentManager manager= getSlaveDocumentManager(); |
| if (manager != null) { |
| IDocument slave= getVisibleDocument(); |
| IDocument master= manager.getMasterDocument(slave); |
| if (master != null) { |
| setVisibleDocument(master); |
| manager.freeSlaveDocument(slave); |
| } |
| } |
| } |
| |
| |
| //-------------------------------------- |
| |
| @Override |
| public void setTextDoubleClickStrategy(ITextDoubleClickStrategy strategy, String contentType) { |
| |
| if (strategy != null) { |
| if (fDoubleClickStrategies == null) |
| fDoubleClickStrategies= new HashMap<>(); |
| fDoubleClickStrategies.put(contentType, strategy); |
| } else if (fDoubleClickStrategies != null) |
| fDoubleClickStrategies.remove(contentType); |
| } |
| |
| /** |
| * Selects from the given map the one which is registered under the content type of the |
| * partition in which the given offset is located. |
| * |
| * @param offset the offset for which to find the plug-in |
| * @param plugins the map from which to choose |
| * @return the plug-in registered under the offset's content type or <code>null</code> if none |
| */ |
| protected Object selectContentTypePlugin(int offset, Map<String, ?> plugins) { |
| final IDocument document= getDocument(); |
| if (document == null) |
| return null; |
| try { |
| return selectContentTypePlugin(TextUtilities.getContentType(document, getDocumentPartitioning(), offset, true), plugins); |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.selectContentTypePlugin")); //$NON-NLS-1$ |
| } |
| return null; |
| } |
| |
| /** |
| * Selects from the given <code>plug-ins</code> this one which is |
| * registered for the given content <code>type</code>. |
| * |
| * @param type the type to be used as lookup key |
| * @param plugins the table to be searched |
| * @return the plug-in in the map for the given content type |
| */ |
| private Object selectContentTypePlugin(String type, Map<String, ?> plugins) { |
| |
| if (plugins == null) |
| return null; |
| |
| return plugins.get(type); |
| } |
| |
| /** |
| * Hook called on receipt of a <code>VerifyEvent</code>. The event has |
| * been translated into a <code>DocumentCommand</code> which can now be |
| * manipulated by interested parties. By default, the hook forwards the command |
| * to the installed instances of <code>IAutoEditStrategy</code>. |
| * |
| * @param command the document command representing the verify event |
| */ |
| protected void customizeDocumentCommand(DocumentCommand command) { |
| if (isIgnoringAutoEditStrategies()) |
| return; |
| |
| IDocument document= getDocument(); |
| |
| if (fTabsToSpacesConverter != null) |
| fTabsToSpacesConverter.customizeDocumentCommand(document, command); |
| |
| @SuppressWarnings("unchecked") |
| List<IAutoEditStrategy> strategies= (List<IAutoEditStrategy>) selectContentTypePlugin(command.offset, fAutoIndentStrategies); |
| if (strategies == null) |
| return; |
| |
| switch (strategies.size()) { |
| // optimization |
| case 0: |
| break; |
| |
| case 1: |
| strategies.iterator().next().customizeDocumentCommand(document, command); |
| break; |
| |
| // make iterator robust against adding/removing strategies from within strategies |
| default: |
| strategies= new ArrayList<>(strategies); |
| for (IAutoEditStrategy iAutoEditStrategy : strategies) |
| iAutoEditStrategy.customizeDocumentCommand(document, command); |
| |
| break; |
| } |
| } |
| |
| /** |
| * Handles the verify event issued by the viewer's text widget. |
| * |
| * @see VerifyListener#verifyText(VerifyEvent) |
| * @param e the verify event |
| */ |
| protected void handleVerifyEvent(VerifyEvent e) { |
| |
| if (fEventConsumer != null) { |
| fEventConsumer.processEvent(e); |
| if (!e.doit) |
| return; |
| } |
| |
| if (fTextWidget.getBlockSelection() && (e.text == null || e.text.length() < 2)) { |
| Point sel = fTextWidget.getSelection(); |
| if (fTextWidget.getLineAtOffset(sel.x) != fTextWidget.getLineAtOffset(sel.y)) { |
| verifyEventInBlockSelection(e); |
| return; |
| } |
| } |
| |
| IRegion modelRange= event2ModelRange(e); |
| fDocumentCommand.setEvent(e, modelRange); |
| customizeDocumentCommand(fDocumentCommand); |
| if (!fDocumentCommand.fillEvent(e, modelRange)) { |
| |
| boolean compoundChange= fDocumentCommand.getCommandCount() > 1; |
| try { |
| |
| fVerifyListener.forward(false); |
| |
| if (compoundChange && fUndoManager != null) |
| fUndoManager.beginCompoundChange(); |
| |
| fDocumentCommand.execute(getDocument()); |
| |
| if (fTextWidget != null) { |
| int documentCaret= fDocumentCommand.caretOffset; |
| if (documentCaret == -1) { |
| // old behavior of document command |
| documentCaret= fDocumentCommand.offset + (fDocumentCommand.text == null ? 0 : fDocumentCommand.text.length()); |
| } |
| |
| int widgetCaret= modelOffset2WidgetOffset(documentCaret); |
| if (widgetCaret == -1) { |
| // try to move it to the closest spot |
| IRegion region= getModelCoverage(); |
| if (region != null) { |
| if (documentCaret <= region.getOffset()) |
| widgetCaret= 0; |
| else if (documentCaret >= region.getOffset() + region.getLength()) |
| widgetCaret= getVisibleRegion().getLength(); |
| } |
| } |
| |
| if (widgetCaret != -1) { |
| // there is a valid widget caret |
| fTextWidget.setCaretOffset(widgetCaret); |
| } |
| |
| fTextWidget.showSelection(); |
| } |
| } catch (BadLocationException x) { |
| |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.verifyText")); //$NON-NLS-1$ |
| |
| } finally { |
| |
| if (compoundChange && fUndoManager != null) |
| fUndoManager.endCompoundChange(); |
| |
| fVerifyListener.forward(true); |
| |
| } |
| } |
| } |
| |
| /** |
| * Simulates typing behavior in block selection mode. |
| * |
| * @param e the verify event. |
| * @since 3.5 |
| */ |
| private void verifyEventInBlockSelection(final VerifyEvent e) { |
| /* |
| Implementation Note: StyledText sends a sequence of n events |
| for a single character typed, where n is the number of affected lines. Since |
| the events share no manifest attribute to group them together or to detect the last event |
| of a sequence, we simulate the modification at the first event and veto any following |
| events with an equal event time. |
| |
| See also bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=268044 |
| */ |
| e.doit= false; |
| boolean isFirst= e.time != fLastEventTime; |
| fLastEventTime= e.time; |
| if (isFirst) { |
| wrapCompoundChange(() -> { |
| SelectionProcessor processor= new SelectionProcessor(TextViewer.this); |
| try { |
| /* Use the selection instead of the event's coordinates. Is this dangerous? */ |
| ISelection selection= getSelection(); |
| int length= e.text.length(); |
| if (length == 0 && e.character == '\0') { |
| // backspace in StyledText block selection mode... |
| TextEdit edit= processor.backspace(selection); |
| edit.apply(fDocument, TextEdit.UPDATE_REGIONS); |
| ISelection empty= processor.makeEmpty(selection, true); |
| setSelection(empty); |
| } else { |
| int lines= processor.getCoveredLines(selection); |
| String delim= fDocument.getLegalLineDelimiters()[0]; |
| StringBuilder text= new StringBuilder(lines * length + (lines - 1) * delim.length()); |
| text.append(e.text); |
| for (int i= 0; i < lines - 1; i++) { |
| text.append(delim); |
| text.append(e.text); |
| } |
| processor.doReplace(selection, text.toString()); |
| } |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.verifyText")); //$NON-NLS-1$ |
| } |
| }); |
| } |
| } |
| |
| //---- text manipulation |
| |
| /** |
| * Returns whether the marked region of this viewer is empty. |
| * |
| * @return <code>true</code> if the marked region of this viewer is empty, otherwise <code>false</code> |
| * @since 2.0 |
| */ |
| private boolean isMarkedRegionEmpty() { |
| return |
| fTextWidget == null || |
| fMarkPosition == null || |
| fMarkPosition.isDeleted() || |
| modelRange2WidgetRange(fMarkPosition) == null; |
| } |
| |
| @Override |
| public boolean canDoOperation(int operation) { |
| |
| if (fTextWidget == null || !redraws()) |
| return false; |
| |
| switch (operation) { |
| case CUT: |
| return isEditable() && (fTextWidget.isTextSelected() || !isMarkedRegionEmpty()); |
| case COPY: |
| return fTextWidget.isTextSelected() || !isMarkedRegionEmpty(); |
| case DELETE: |
| case PASTE: |
| return isEditable(); |
| case SELECT_ALL: |
| return true; |
| case SHIFT_LEFT: |
| case SHIFT_RIGHT: |
| return isEditable() && fIndentChars != null && areMultipleLinesSelected(); |
| case PREFIX: |
| case STRIP_PREFIX: |
| return isEditable() && fDefaultPrefixChars != null; |
| case UNDO: |
| return fUndoManager != null && fUndoManager.undoable(); |
| case REDO: |
| return fUndoManager != null && fUndoManager.redoable(); |
| case PRINT: |
| return isPrintable(); |
| case HyperlinkManager.OPEN_HYPERLINK: |
| return fHyperlinkManager != null; |
| |
| // Workaround to fix bug 434791 during 4.4 RC2. Will be replaced by official API during 4.5. |
| case -100: |
| return true; |
| |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public void doOperation(int operation) { |
| |
| if (fTextWidget == null || !redraws()) |
| return; |
| |
| Point selection= null; |
| |
| switch (operation) { |
| |
| case UNDO: |
| if (fUndoManager != null) { |
| ignoreAutoEditStrategies(true); |
| fUndoManager.undo(); |
| ignoreAutoEditStrategies(false); |
| } |
| break; |
| case REDO: |
| if (fUndoManager != null) { |
| ignoreAutoEditStrategies(true); |
| fUndoManager.redo(); |
| ignoreAutoEditStrategies(false); |
| } |
| break; |
| case CUT: |
| if (!fTextWidget.isTextSelected()) |
| copyMarkedRegion(true); |
| else |
| wrapCompoundChange(() -> fTextWidget.cut()); |
| |
| selection= fTextWidget.getSelectionRange(); |
| fireSelectionChanged(selection.x, selection.y); |
| |
| break; |
| case COPY: |
| if (!fTextWidget.isTextSelected()) |
| copyMarkedRegion(false); |
| else |
| fTextWidget.copy(); |
| break; |
| case PASTE: |
| paste(); |
| break; |
| case DELETE: |
| delete(); |
| break; |
| case SELECT_ALL: { |
| IDocument doc= getDocument(); |
| if (doc != null) { |
| if (fTextWidget.getBlockSelection()) |
| // XXX: performance hack: use 1000 for the endColumn - StyledText will not select more than what's possible in the viewport. |
| setSelection(new BlockTextSelection(doc, 0, 0, doc.getNumberOfLines() - 1, 1000, fTextWidget.getTabs())); |
| else |
| setSelectedRange(0, doc.getLength()); |
| } |
| break; |
| } |
| case SHIFT_RIGHT: |
| shift(false, true, false); |
| break; |
| case SHIFT_LEFT: |
| shift(false, false, false); |
| break; |
| case PREFIX: |
| shift(true, true, true); |
| break; |
| case STRIP_PREFIX: |
| shift(true, false, true); |
| break; |
| case PRINT: |
| print(); |
| break; |
| case HyperlinkManager.OPEN_HYPERLINK: |
| boolean atleastOneLinkOpened= fHyperlinkManager.openHyperlink(); |
| if (!atleastOneLinkOpened) |
| MessageDialog.openInformation(getControl().getShell(), |
| JFaceTextMessages.getString("TextViewer.open_hyperlink_error_title"), JFaceTextMessages.getString("TextViewer.open_hyperlink_error_message")); //$NON-NLS-1$ //$NON-NLS-2$ |
| return; |
| |
| // Workaround to fix bug 434791 during 4.4 RC2. Will be replaced by official API during 4.5. |
| case -100: |
| if (fLastSentSelectionChange != null) { |
| ISelection lastSelection= new TextSelection(getDocument(), fLastSentSelectionChange.getOffset(), fLastSentSelectionChange.getLength()); |
| fireSelectionChanged(new SelectionChangedEvent(this, lastSelection)); |
| } |
| return; |
| |
| } |
| } |
| |
| private void delete() { |
| if (!fTextWidget.getBlockSelection()) { |
| fTextWidget.invokeAction(ST.DELETE_NEXT); |
| } else { |
| wrapCompoundChange(() -> { |
| try { |
| new SelectionProcessor(TextViewer.this).doDelete(getSelection()); |
| } catch (BadLocationException e) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.delete")); //$NON-NLS-1$ |
| } |
| }); |
| } |
| Point selection= fTextWidget.getSelectionRange(); |
| fireSelectionChanged(selection.x, selection.y); |
| } |
| |
| private void paste() { |
| // ignoreAutoEditStrategies(true); |
| if (!fTextWidget.getBlockSelection()) { |
| fTextWidget.paste(); |
| } else { |
| wrapCompoundChange(() -> { |
| SelectionProcessor processor= new SelectionProcessor(TextViewer.this); |
| Clipboard clipboard= new Clipboard(getDisplay()); |
| try { |
| /* |
| * Paste in block selection mode. If the pasted text is not a multi-line |
| * text, pasting behaves like typing, i.e. the pasted text replaces |
| * the selection on each line. If the pasted text is multi-line (e.g. from |
| * copying a column selection), the selection is replaced, line-by-line, by |
| * the corresponding contents of the pasted text. If the selection touches |
| * more lines than the pasted text, the selection on the remaining lines |
| * is deleted (assuming an empty text being pasted). If the pasted |
| * text contains more lines than the selection, the selection is extended |
| * to the succeeding lines, or more lines are added to accommodate the |
| * paste operation. |
| */ |
| ISelection selection= getSelection(); |
| TextTransfer plainTextTransfer= TextTransfer.getInstance(); |
| String contents= (String) clipboard.getContents(plainTextTransfer, DND.CLIPBOARD); |
| String toInsert; |
| if (TextUtilities.indexOf(fDocument.getLegalLineDelimiters(), contents, 0)[0] != -1) { |
| // multi-line insertion |
| toInsert= contents; |
| } else { |
| // single-line insertion |
| int length= contents.length(); |
| int lines= processor.getCoveredLines(selection); |
| String delim= fDocument.getLegalLineDelimiters()[0]; |
| StringBuilder text= new StringBuilder(lines * length + (lines - 1) * delim.length()); |
| text.append(contents); |
| for (int i= 0; i < lines - 1; i++) { |
| text.append(delim); |
| text.append(contents); |
| } |
| toInsert= text.toString(); |
| } |
| processor.doReplace(selection, toInsert); |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.paste")); //$NON-NLS-1$ |
| } finally { |
| clipboard.dispose(); |
| } |
| }); |
| } |
| Point selection= fTextWidget.getSelectionRange(); |
| fireSelectionChanged(selection.x, selection.y); |
| // ignoreAutoEditStrategies(false); |
| } |
| |
| /** |
| * If the text widget is in {@link StyledText#getBlockSelection() block selection mode}, the |
| * passed code is wrapped into a begin/endCompoundChange undo session on the |
| * {@linkplain #getRewriteTarget() rewrite target}; otherwise, the runnable is executed |
| * directly. |
| * |
| * @param runnable the code to wrap when in block selection mode |
| * @since 3.5 |
| */ |
| private void wrapCompoundChange(Runnable runnable) { |
| if (!fTextWidget.getBlockSelection()) { |
| runnable.run(); |
| return; |
| } |
| IRewriteTarget target= getRewriteTarget(); |
| target.beginCompoundChange(); |
| try { |
| runnable.run(); |
| } finally { |
| target.endCompoundChange(); |
| } |
| |
| } |
| |
| /** |
| * Tells this viewer whether the registered auto edit strategies should be ignored. |
| * |
| * @param ignore <code>true</code> if the strategies should be ignored. |
| * @since 2.1 |
| */ |
| protected void ignoreAutoEditStrategies(boolean ignore) { |
| if (fIgnoreAutoIndent == ignore) |
| return; |
| |
| fIgnoreAutoIndent= ignore; |
| |
| IDocument document= getDocument(); |
| if (document instanceof IDocumentExtension2) { |
| IDocumentExtension2 extension= (IDocumentExtension2) document; |
| if (ignore) |
| extension.ignorePostNotificationReplaces(); |
| else |
| extension.acceptPostNotificationReplaces(); |
| } |
| } |
| |
| /** |
| * Returns whether this viewer ignores the registered auto edit strategies. |
| * |
| * @return <code>true</code> if the strategies are ignored |
| * @since 2.1 |
| */ |
| protected boolean isIgnoringAutoEditStrategies() { |
| return fIgnoreAutoIndent; |
| } |
| |
| @Override |
| public void enableOperation(int operation, boolean enable) { |
| /* |
| * NO-OP by default. |
| * Will be changed to regularly disable the known operations. |
| */ |
| } |
| |
| /** |
| * Copies/cuts the marked region. |
| * |
| * @param delete <code>true</code> if the region should be deleted rather than copied. |
| * @since 2.0 |
| */ |
| protected void copyMarkedRegion(boolean delete) { |
| |
| if (fTextWidget == null) |
| return; |
| |
| if (fMarkPosition == null || fMarkPosition.isDeleted() || modelRange2WidgetRange(fMarkPosition) == null) |
| return; |
| |
| int widgetMarkOffset= modelOffset2WidgetOffset(fMarkPosition.offset); |
| Point selection= fTextWidget.getSelection(); |
| if (selection.x <= widgetMarkOffset) |
| fTextWidget.setSelection(selection.x, widgetMarkOffset); |
| else |
| fTextWidget.setSelection(widgetMarkOffset, selection.x); |
| |
| if (delete) { |
| wrapCompoundChange(() -> fTextWidget.cut()); |
| } else { |
| fTextWidget.copy(); |
| fTextWidget.setSelection(selection.x); // restore old cursor position |
| } |
| } |
| |
| /** |
| * Deletes the current selection. If the selection has the length 0 |
| * the selection is automatically extended to the right - either by 1 |
| * or by the length of line delimiter if at the end of a line. |
| * |
| * @deprecated use <code>StyledText.invokeAction</code> instead |
| */ |
| @Deprecated |
| protected void deleteText() { |
| fTextWidget.invokeAction(ST.DELETE_NEXT); |
| } |
| |
| /** |
| * A block is selected if the character preceding the start of the |
| * selection is a new line character. |
| * |
| * @return <code>true</code> if a block is selected |
| */ |
| protected boolean isBlockSelected() { |
| |
| Point s= getSelectedRange(); |
| if (s.y == 0) |
| return false; |
| |
| try { |
| |
| IDocument document= getDocument(); |
| int line= document.getLineOfOffset(s.x); |
| int start= document.getLineOffset(line); |
| return (s.x == start); |
| |
| } catch (BadLocationException x) { |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns <code>true</code> if one line is completely selected or if multiple lines are selected. |
| * Being completely selected means that all characters except the new line characters are |
| * selected. |
| * |
| * @return <code>true</code> if one or multiple lines are selected |
| * @since 2.0 |
| */ |
| protected boolean areMultipleLinesSelected() { |
| Point s= getSelectedRange(); |
| if (s.y == 0) |
| return false; |
| |
| try { |
| |
| IDocument document= getDocument(); |
| int startLine= document.getLineOfOffset(s.x); |
| int endLine= document.getLineOfOffset(s.x + s.y); |
| IRegion line= document.getLineInformation(startLine); |
| return startLine != endLine || (s.x == line.getOffset() && s.y == line.getLength()); |
| |
| } catch (BadLocationException x) { |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the index of the first line whose start offset is in the given text range. |
| * |
| * @param region the text range in characters where to find the line |
| * @return the first line whose start index is in the given range, -1 if there is no such line |
| */ |
| private int getFirstCompleteLineOfRegion(IRegion region) { |
| |
| try { |
| |
| IDocument d= getDocument(); |
| |
| int startLine= d.getLineOfOffset(region.getOffset()); |
| |
| int offset= d.getLineOffset(startLine); |
| if (offset >= region.getOffset()) |
| return startLine; |
| |
| offset= d.getLineOffset(startLine + 1); |
| return (offset > region.getOffset() + region.getLength() ? -1 : startLine + 1); |
| |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.getFirstCompleteLineOfRegion")); //$NON-NLS-1$ |
| } |
| |
| return -1; |
| } |
| |
| |
| /** |
| * Creates a region describing the text block (something that starts at |
| * the beginning of a line) completely containing the current selection. |
| * |
| * @param selection the selection to use |
| * @return the region describing the text block comprising the given selection |
| * @throws BadLocationException when the document does not contain the selection |
| */ |
| private IRegion getTextBlockFromSelection(ITextSelection selection) throws BadLocationException { |
| IDocument document= getDocument(); |
| int start= document.getLineOffset(selection.getStartLine()); |
| int end; |
| int endLine= selection.getEndLine(); |
| if (document.getNumberOfLines() > endLine+1) { |
| end= document.getLineOffset(endLine+1); |
| } else { |
| end= document.getLength(); |
| } |
| return new Region(start, end - start); |
| } |
| |
| /** |
| * Shifts a text block to the right or left using the specified set of prefix characters. |
| * The prefixes must start at the beginning of the line. |
| * |
| * @param useDefaultPrefixes says whether the configured default or indent prefixes should be used |
| * @param right says whether to shift to the right or the left |
| * |
| * @deprecated use shift(boolean, boolean, boolean) instead |
| */ |
| @Deprecated |
| protected void shift(boolean useDefaultPrefixes, boolean right) { |
| shift(useDefaultPrefixes, right, false); |
| } |
| |
| /** |
| * Shifts a text block to the right or left using the specified set of prefix characters. |
| * If white space should be ignored the prefix characters must not be at the beginning of |
| * the line when shifting to the left. There may be whitespace in front of the prefixes. |
| * |
| * @param useDefaultPrefixes says whether the configured default or indent prefixes should be used |
| * @param right says whether to shift to the right or the left |
| * @param ignoreWhitespace says whether whitespace in front of prefixes is allowed |
| * @since 2.0 |
| */ |
| protected void shift(boolean useDefaultPrefixes, boolean right, boolean ignoreWhitespace) { |
| if (fUndoManager != null) |
| fUndoManager.beginCompoundChange(); |
| |
| IDocument d= getDocument(); |
| Map<String, IDocumentPartitioner> partitioners= null; |
| DocumentRewriteSession rewriteSession= null; |
| try { |
| ITextSelection selection= (ITextSelection) getSelection(); |
| IRegion block= getTextBlockFromSelection(selection); |
| ITypedRegion[] regions= TextUtilities.computePartitioning(d, getDocumentPartitioning(), block.getOffset(), block.getLength(), false); |
| |
| int lineCount= 0; |
| int[] lines= new int[regions.length * 2]; // [start line, end line, start line, end line, ...] |
| for (int i= 0, j= 0; i < regions.length; i++, j+= 2) { |
| // start line of region |
| lines[j]= getFirstCompleteLineOfRegion(regions[i]); |
| // end line of region |
| int length= regions[i].getLength(); |
| int offset= regions[i].getOffset() + length; |
| if (length > 0) |
| offset--; |
| lines[j + 1]= (lines[j] == -1 ? -1 : d.getLineOfOffset(offset)); |
| lineCount += lines[j + 1] - lines[j] + 1; |
| } |
| |
| if (d instanceof IDocumentExtension4) { |
| IDocumentExtension4 extension= (IDocumentExtension4) d; |
| rewriteSession= extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL); |
| } else { |
| setRedraw(false); |
| startSequentialRewriteMode(true); |
| } |
| if (lineCount >= 20) |
| partitioners= TextUtilities.removeDocumentPartitioners(d); |
| |
| // Perform the shift operation. |
| Map<String, String[]> map= (useDefaultPrefixes ? fDefaultPrefixChars : fIndentChars); |
| for (int i= 0, j= 0; i < regions.length; i++, j += 2) { |
| String[] prefixes= (String[]) selectContentTypePlugin(regions[i].getType(), map); |
| if (prefixes != null && prefixes.length > 0 && lines[j] >= 0 && lines[j + 1] >= 0) { |
| if (right) |
| shiftRight(lines[j], lines[j + 1], prefixes[0]); |
| else |
| shiftLeft(lines[j], lines[j + 1], prefixes, ignoreWhitespace); |
| } |
| } |
| |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.shift_1")); //$NON-NLS-1$ |
| |
| } finally { |
| |
| if (partitioners != null) |
| TextUtilities.addDocumentPartitioners(d, partitioners); |
| |
| if (d instanceof IDocumentExtension4) { |
| IDocumentExtension4 extension= (IDocumentExtension4) d; |
| extension.stopRewriteSession(rewriteSession); |
| } else { |
| stopSequentialRewriteMode(); |
| setRedraw(true); |
| } |
| |
| if (fUndoManager != null) |
| fUndoManager.endCompoundChange(); |
| } |
| } |
| |
| /** |
| * Shifts the specified lines to the right inserting the given prefix |
| * at the beginning of each line |
| * |
| * @param prefix the prefix to be inserted |
| * @param startLine the first line to shift |
| * @param endLine the last line to shift |
| * @since 2.0 |
| */ |
| private void shiftRight(int startLine, int endLine, String prefix) { |
| |
| try { |
| |
| IDocument d= getDocument(); |
| while (startLine <= endLine) { |
| d.replace(d.getLineOffset(startLine++), 0, prefix); |
| } |
| |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println("TextViewer.shiftRight: BadLocationException"); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Shifts the specified lines to the right or to the left. On shifting to the right |
| * it insert <code>prefixes[0]</code> at the beginning of each line. On shifting to the |
| * left it tests whether each of the specified lines starts with one of the specified |
| * prefixes and if so, removes the prefix. |
| * |
| * @param startLine the first line to shift |
| * @param endLine the last line to shift |
| * @param prefixes the prefixes to be used for shifting |
| * @param ignoreWhitespace <code>true</code> if whitespace should be ignored, <code>false</code> otherwise |
| * @since 2.0 |
| */ |
| private void shiftLeft(int startLine, int endLine, String[] prefixes, boolean ignoreWhitespace) { |
| |
| IDocument d= getDocument(); |
| |
| try { |
| |
| IRegion[] occurrences= new IRegion[endLine - startLine + 1]; |
| |
| // find all the first occurrences of prefix in the given lines |
| for (int i= 0; i < occurrences.length; i++) { |
| |
| IRegion line= d.getLineInformation(startLine + i); |
| String text= d.get(line.getOffset(), line.getLength()); |
| |
| int index= -1; |
| int[] found= TextUtilities.indexOf(prefixes, text, 0); |
| if (found[0] != -1) { |
| if (ignoreWhitespace) { |
| String s= d.get(line.getOffset(), found[0]); |
| s= s.trim(); |
| if (s.length() == 0) |
| index= line.getOffset() + found[0]; |
| } else if (found[0] == 0) |
| index= line.getOffset(); |
| } |
| |
| if (index > -1) { |
| // remember where prefix is in line, so that it can be removed |
| int length= prefixes[found[1]].length(); |
| if (length == 0 && !ignoreWhitespace && line.getLength() > 0) { |
| // found a non-empty line which cannot be shifted |
| return; |
| } |
| occurrences[i]= new Region(index, length); |
| } else { |
| // found a line which cannot be shifted |
| return; |
| } |
| } |
| |
| // OK - change the document |
| int decrement= 0; |
| for (IRegion r : occurrences) { |
| d.replace(r.getOffset() - decrement, r.getLength(), ""); //$NON-NLS-1$ |
| decrement += r.getLength(); |
| } |
| |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println("TextViewer.shiftLeft: BadLocationException"); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Returns whether the shown text can be printed. |
| * |
| * @return the viewer's printable mode |
| */ |
| protected boolean isPrintable() { |
| return true; // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=250528 |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @since 3.4 |
| */ |
| @Override |
| public void print(StyledTextPrintOptions options) { |
| final Shell shell= fTextWidget.getShell(); |
| |
| if (Printer.getPrinterList().length == 0) { |
| String title= JFaceTextMessages.getString("TextViewer.warning.noPrinter.title"); //$NON-NLS-1$ |
| String msg= JFaceTextMessages.getString("TextViewer.warning.noPrinter.message"); //$NON-NLS-1$ |
| MessageDialog.openWarning(shell, title, msg); |
| return; |
| } |
| |
| final PrintDialog dialog= new PrintDialog(shell, SWT.PRIMARY_MODAL); |
| dialog.setPrinterData(fgPrinterData); |
| final PrinterData data= dialog.open(); |
| |
| if (data != null) { |
| final Printer printer= new Printer(data); |
| final Runnable styledTextPrinter= fTextWidget.print(printer, options); |
| |
| Thread printingThread= new Thread("Printing") { //$NON-NLS-1$ |
| @Override |
| public void run() { |
| styledTextPrinter.run(); |
| printer.dispose(); |
| } |
| }; |
| printingThread.start(); |
| |
| /* |
| * FIXME: |
| * Should copy the printer data to avoid threading issues, |
| * but this is currently not possible, see http://bugs.eclipse.org/297957 |
| */ |
| fgPrinterData= data; |
| fgPrinterData.startPage= 1; |
| fgPrinterData.endPage= 1; |
| fgPrinterData.scope= PrinterData.ALL_PAGES; |
| fgPrinterData.copyCount= 1; |
| } |
| } |
| |
| /** |
| * Brings up a print dialog and calls <code>printContents(Printer)</code> |
| * which performs the actual print. |
| */ |
| protected void print() { |
| StyledTextPrintOptions options= new StyledTextPrintOptions(); |
| options.printTextFontStyle= true; |
| options.printTextForeground= true; |
| print(options); |
| } |
| |
| //------ find support |
| |
| /** |
| * Adheres to the contract of {@link IFindReplaceTarget#canPerformFind()}. |
| * |
| * @return <code>true</code> if find can be performed, <code>false</code> otherwise |
| */ |
| protected boolean canPerformFind() { |
| IDocument d= getVisibleDocument(); |
| return (fTextWidget != null && d != null && d.getLength() > 0); |
| } |
| |
| /** |
| * Adheres to the contract of {@link IFindReplaceTarget#findAndSelect(int, String, boolean, boolean, boolean)}. |
| * |
| * @param startPosition the start position |
| * @param findString the find string specification |
| * @param forwardSearch the search direction |
| * @param caseSensitive <code>true</code> if case sensitive, <code>false</code> otherwise |
| * @param wholeWord <code>true</code> if match must be whole words, <code>false</code> otherwise |
| * @return the model offset of the first match |
| * @deprecated as of 3.0 use {@link #findAndSelect(int, String, boolean, boolean, boolean, boolean)} |
| */ |
| @Deprecated |
| protected int findAndSelect(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord) { |
| try { |
| return findAndSelect(startPosition, findString, forwardSearch, caseSensitive, wholeWord, false); |
| } catch (IllegalStateException ex) { |
| return -1; |
| } catch (PatternSyntaxException ex) { |
| return -1; |
| } |
| } |
| |
| /** |
| * Adheres to the contract of |
| * {@link org.eclipse.jface.text.IFindReplaceTargetExtension3#findAndSelect(int, String, boolean, boolean, boolean, boolean)}. |
| * |
| * @param startPosition the start position |
| * @param findString the find string specification |
| * @param forwardSearch the search direction |
| * @param caseSensitive <code>true</code> if case sensitive, <code>false</code> otherwise |
| * @param wholeWord <code>true</code> if matches must be whole words, <code>false</code> otherwise |
| * @param regExSearch <code>true</code> if <code>findString</code> is a regular expression, <code>false</code> otherwise |
| * @return the model offset of the first match |
| * |
| */ |
| protected int findAndSelect(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, boolean regExSearch) { |
| if (fTextWidget == null) |
| return -1; |
| |
| try { |
| |
| int widgetOffset= (startPosition == -1 ? startPosition : modelOffset2WidgetOffset(startPosition)); |
| FindReplaceDocumentAdapter adapter= getFindReplaceDocumentAdapter(); |
| IRegion matchRegion= adapter.find(widgetOffset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); |
| if (matchRegion != null) { |
| int widgetPos= matchRegion.getOffset(); |
| int length= matchRegion.getLength(); |
| |
| // Prevents setting of widget selection with line delimiters at beginning or end |
| char startChar= adapter.charAt(widgetPos); |
| char endChar= adapter.charAt(widgetPos+length-1); |
| boolean borderHasLineDelimiter= startChar == '\n' || startChar == '\r' || endChar == '\n' || endChar == '\r'; |
| boolean redraws= redraws(); |
| if (borderHasLineDelimiter && redraws) |
| setRedraw(false); |
| |
| if (redraws()) { |
| fTextWidget.setSelectionRange(widgetPos, length); |
| internalRevealRange(widgetPos, widgetPos + length); |
| selectionChanged(widgetPos, length); |
| } else { |
| setSelectedRange(widgetOffset2ModelOffset(widgetPos), length); |
| if (redraws) |
| setRedraw(true); |
| } |
| |
| return widgetOffset2ModelOffset(widgetPos); |
| } |
| |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.findAndSelect")); //$NON-NLS-1$ |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Adheres to the contract of {@link org.eclipse.jface.text.IFindReplaceTargetExtension3#findAndSelect(int, String, boolean, boolean, boolean, boolean)}. |
| * |
| * @param startPosition the start position |
| * @param findString the find string specification |
| * @param forwardSearch the search direction |
| * @param caseSensitive <code>true</code> if case sensitive, <code>false</code> otherwise |
| * @param wholeWord <code>true</code> if matches must be whole words, <code>false</code> otherwise |
| * @param rangeOffset the search scope offset |
| * @param rangeLength the search scope length |
| * @param regExSearch <code>true</code> if <code>findString</code> is a regular expression, <code>false</code> otherwise |
| * @return the model offset of the first match |
| * @since 3.0 |
| */ |
| protected int findAndSelectInRange(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, int rangeOffset, int rangeLength, boolean regExSearch) { |
| if (fTextWidget == null) |
| return -1; |
| |
| try { |
| |
| int modelOffset; |
| if (forwardSearch && (startPosition == -1 || startPosition < rangeOffset)) { |
| modelOffset= rangeOffset; |
| } else if (!forwardSearch && (startPosition == -1 || startPosition > rangeOffset + rangeLength)) { |
| modelOffset= rangeOffset + rangeLength; |
| } else { |
| modelOffset= startPosition; |
| } |
| |
| int widgetOffset= modelOffset2WidgetOffset(modelOffset); |
| if (widgetOffset == -1) |
| return -1; |
| |
| FindReplaceDocumentAdapter adapter= getFindReplaceDocumentAdapter(); |
| IRegion matchRegion= adapter.find(widgetOffset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); |
| int widgetPos= -1; |
| int length= 0; |
| if (matchRegion != null) { |
| widgetPos= matchRegion.getOffset(); |
| length= matchRegion.getLength(); |
| } |
| int modelPos= widgetPos == -1 ? -1 : widgetOffset2ModelOffset(widgetPos); |
| |
| if (widgetPos != -1 && (modelPos < rangeOffset || modelPos + length > rangeOffset + rangeLength)) |
| widgetPos= -1; |
| |
| if (widgetPos > -1) { |
| |
| // Prevents setting of widget selection with line delimiters at beginning or end |
| char startChar= adapter.charAt(widgetPos); |
| char endChar= adapter.charAt(widgetPos+length-1); |
| boolean borderHasLineDelimiter= startChar == '\n' || startChar == '\r' || endChar == '\n' || endChar == '\r'; |
| boolean redraws= redraws(); |
| if (borderHasLineDelimiter && redraws) |
| setRedraw(false); |
| |
| if (redraws()) { |
| fTextWidget.setSelectionRange(widgetPos, length); |
| internalRevealRange(widgetPos, widgetPos + length); |
| selectionChanged(widgetPos, length); |
| } else { |
| setSelectedRange(modelPos, length); |
| if (redraws) |
| setRedraw(true); |
| } |
| |
| return modelPos; |
| } |
| |
| |
| } catch (BadLocationException x) { |
| if (TRACE_ERRORS) |
| System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.findAndSelect")); //$NON-NLS-1$ |
| } |
| |
| return -1; |
| } |
| |
| //---------- text presentation support |
| |
| @Override |
| public void setTextColor(Color color) { |
| if (color != null) |
| setTextColor(color, 0, getDocument().getLength(), true); |
| } |
| |
| @Override |
| public void setTextColor(Color color, int start, int length, boolean controlRedraw) { |
| if (fTextWidget != null) { |
| |
| StyleRange s= new StyleRange(); |
| s.foreground= color; |
| s.start= start; |
| s.length= length; |
| |
| s= modelStyleRange2WidgetStyleRange(s); |
| if (s != null) { |
| if (controlRedraw) |
| fTextWidget.setRedraw(false); |
| try { |
| fTextWidget.setStyleRange(s); |
| } finally { |
| if (controlRedraw) |
| fTextWidget.setRedraw(true); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds the given presentation to the viewer's style information. |
| * |
| * @param presentation the presentation to be added |
| */ |
| private void addPresentation(TextPresentation presentation) { |
| |
| StyleRange range= presentation.getDefaultStyleRange(); |
| if (range != null) { |
| |
| range= modelStyleRange2WidgetStyleRange(range); |
| if (range != null) |
| fTextWidget.setStyleRange(range); |
| |
| ArrayList<StyleRange> ranges= new ArrayList<>(presentation.getDenumerableRanges()); |
| Iterator<StyleRange> e= presentation.getNonDefaultStyleRangeIterator(); |
| while (e.hasNext()) { |
| range= e.next(); |
| range= modelStyleRange2WidgetStyleRange(range); |
| if (range != null) |
| ranges.add(range); |
| } |
| |
| if (!ranges.isEmpty()) |
| fTextWidget.replaceStyleRanges(0, 0, ranges.toArray(new StyleRange[ranges.size()])); |
| |
| } else { |
| IRegion region= modelRange2WidgetRange(presentation.getCoverage()); |
| if (region == null) |
| return; |
| |
| List<StyleRange> list= new ArrayList<>(presentation.getDenumerableRanges()); |
| Iterator<StyleRange> e= presentation.getAllStyleRangeIterator(); |
| while (e.hasNext()) { |
| range= e.next(); |
| range= modelStyleRange2WidgetStyleRange(range); |
| if (range != null) |
| list.add(range); |
| } |
| |
| if (!list.isEmpty()) { |
| StyleRange[] ranges= new StyleRange[list.size()]; |
| list.toArray(ranges); |
| fTextWidget.replaceStyleRanges(region.getOffset(), region.getLength(), ranges); |
| } |
| } |
| } |
| |
| /** |
| * Applies the given presentation to the given text widget. Helper method. |
| * |
| * @param presentation the style information |
| * @since 2.1 |
| */ |
| private void applyTextPresentation(TextPresentation presentation) { |
| |
| List<StyleRange> list= new ArrayList<>(presentation.getDenumerableRanges()); |
| Iterator<StyleRange> e= presentation.getAllStyleRangeIterator(); |
| while (e.hasNext()) { |
| StyleRange range= e.next(); |
| range= modelStyleRange2WidgetStyleRange(range); |
| if (range != null) |
| list.add(range); |
| } |
| |
| if (!list.isEmpty()) { |
| StyleRange[] ranges= new StyleRange[list.size()]; |
| list.toArray(ranges); |
| fTextWidget.setStyleRanges(ranges); |
| } |
| } |
| |
| /** |
| * Returns the visible region if it is not equal to the whole document. |
| * Otherwise returns <code>null</code>. |
| * |
| * @return the viewer's visible region if smaller than input document, otherwise <code>null</code> |
| */ |
| protected IRegion _internalGetVisibleRegion() { |
| |
| IDocument document= getVisibleDocument(); |
| if (document instanceof ChildDocument) { |
| Position p= ((ChildDocument) document).getParentDocumentRange(); |
| return new Region(p.getOffset(), p.getLength()); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public void changeTextPresentation(TextPresentation presentation, boolean controlRedraw) { |
| |
| if (presentation == null || !redraws()) |
| return; |
| |
| if (fTextWidget == null) |
| return; |
| |
| |
| /* |
| * Call registered text presentation listeners |
| * and let them apply their presentation. |
| */ |
| if (fTextPresentationListeners != null) { |
| ArrayList<ITextPresentationListener> listeners= new ArrayList<>(fTextPresentationListeners); |
| for (int i= 0, size= listeners.size(); i < size; i++) { |
| ITextPresentationListener listener= listeners.get(i); |
| listener.applyTextPresentation(presentation); |
| } |
| } |
| |
| if (presentation.isEmpty()) |
| return; |
| |
| if (controlRedraw) |
| fTextWidget.setRedraw(false); |
| |
| if (fReplaceTextPresentation) |
| applyTextPresentation(presentation); |
| else |
| addPresentation(presentation); |
| |
| if (controlRedraw) |
| fTextWidget.setRedraw(true); |
| } |
| |
| @Override |
| public IFindReplaceTarget getFindReplaceTarget() { |
| if (fFindReplaceTarget == null) |
| fFindReplaceTarget= new FindReplaceTarget(); |
| return fFindReplaceTarget; |
| } |
| |
| /** |
| * Returns the find/replace document adapter. |
| * |
| * @return the find/replace document adapter. |
| * @since 3.0 |
| */ |
| protected FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() { |
| if (fFindReplaceDocumentAdapter == null) |
| fFindReplaceDocumentAdapter= new FindReplaceDocumentAdapter(getVisibleDocument()); |
| return fFindReplaceDocumentAdapter; |
| } |
| |
| @Override |
| public ITextOperationTarget getTextOperationTarget() { |
| return this; |
| } |
| |
| @Override |
| public void appendVerifyKeyListener(VerifyKeyListener listener) { |
| int index= fVerifyKeyListenersManager.numberOfListeners(); |
| fVerifyKeyListenersManager.insertListener(listener, index); |
| } |
| |
| @Override |
| public void prependVerifyKeyListener(VerifyKeyListener listener) { |
| fVerifyKeyListenersManager.insertListener(listener, 0); |
| |
| } |
| |
| @Override |
| public void removeVerifyKeyListener(VerifyKeyListener listener) { |
| fVerifyKeyListenersManager.removeListener(listener); |
| } |
| |
| @Override |
| public int getMark() { |
| return fMarkPosition == null || fMarkPosition.isDeleted() ? -1 : fMarkPosition.getOffset(); |
| } |
| |
| @Override |
| public void setMark(int offset) { |
| |
| // clear |
| if (offset == -1) { |
| if (fMarkPosition != null && !fMarkPosition.isDeleted()) { |
| |
| IDocument document= getDocument(); |
| if (document != null) |
| document.removePosition(fMarkPosition); |
| } |
| |
| fMarkPosition= null; |
| |
| markChanged(-1, 0); |
| |
| // set |
| } else { |
| |
| IDocument document= getDocument(); |
| if (document == null) { |
| fMarkPosition= null; |
| return; |
| } |
| |
| if (fMarkPosition != null) |
| document.removePosition(fMarkPosition); |
| |
| fMarkPosition= null; |
| |
| try { |
| |
| Position position= new Position(offset); |
| document.addPosition(MARK_POSITION_CATEGORY, position); |
| fMarkPosition= position; |
| |
| } catch (BadLocationException e) { |
| return; |
| } catch (BadPositionCategoryException e) { |
| return; |
| } |
| |
| markChanged(modelOffset2WidgetOffset(fMarkPosition.offset), 0); |
| } |
| } |
| |
| @Override |
| protected void inputChanged(Object newInput, Object oldInput) { |
| |
| IDocument oldDocument= (IDocument) oldInput; |
| if (oldDocument != null) { |
| |
| if (fMarkPosition != null && !fMarkPosition.isDeleted()) |
| oldDocument.removePosition(fMarkPosition); |
| |
| try { |
| oldDocument.removePositionUpdater(fMarkPositionUpdater); |
| oldDocument.removePositionCategory(MARK_POSITION_CATEGORY); |
| |
| } catch (BadPositionCategoryException e) { |
| } |
| } |
| |
| fMarkPosition= null; |
| |
| if (oldDocument instanceof IDocumentExtension4) { |
| IDocumentExtension4 document= (IDocumentExtension4) oldDocument; |
| document.removeDocumentRewriteSessionListener(fDocumentRewriteSessionListener); |
| } |
| |
| super.inputChanged(newInput, oldInput); |
| |
| if (newInput instanceof IDocumentExtension4) { |
| IDocumentExtension4 document= (IDocumentExtension4) newInput; |
| document.addDocumentRewriteSessionListener(fDocumentRewriteSessionListener); |
| } |
| |
| IDocument newDocument= (IDocument) newInput; |
| if (newDocument != null) { |
| newDocument.addPositionCategory(MARK_POSITION_CATEGORY); |
| newDocument.addPositionUpdater(fMarkPositionUpdater); |
| } |
| } |
| |
| /** |
| * Informs all text listeners about the change of the viewer's redraw state. |
| * @since 2.0 |
| */ |
| private void fireRedrawChanged() { |
| fWidgetCommand.start= 0; |
| fWidgetCommand.length= 0; |
| fWidgetCommand.text= null; |
| fWidgetCommand.event= null; |
| updateTextListeners(fWidgetCommand); |
| } |
| |
| /** |
| * Enables the redrawing of this text viewer. |
| * @since 2.0 |
| */ |
| protected void enabledRedrawing() { |
| enabledRedrawing(-1); |
| } |
| /** |
| * Enables the redrawing of this text viewer. |
| * |
| * @param topIndex the top index to be set or <code>-1</code> |
| * @since 3.0 |
| */ |
| protected void enabledRedrawing(int topIndex) { |
| if (fDocumentAdapter instanceof IDocumentAdapterExtension) { |
| IDocumentAdapterExtension extension= (IDocumentAdapterExtension) fDocumentAdapter; |
| StyledText textWidget= getTextWidget(); |
| if (textWidget != null && !textWidget.isDisposed()) { |
| extension.resumeForwardingDocumentChanges(); |
| if (topIndex > -1) { |
| try { |
| setTopIndex(topIndex); |
| } catch (IllegalArgumentException x) { |
| // changes don't allow for the previous top pixel |
| } |
| } |
| } |
| } |
| |
| if (fViewerState != null) { |
| fViewerState.restore(topIndex == -1); |
| fViewerState= null; |
| } |
| |
| if (fTextWidget != null && !fTextWidget.isDisposed()) |
| fTextWidget.setRedraw(true); |
| |
| fireRedrawChanged(); |
| } |
| |
| /** |
| * Disables the redrawing of this text viewer. Subclasses may extend. |
| * @since 2.0 |
| */ |
| protected void disableRedrawing() { |
| if (fViewerState == null) |
| fViewerState= new ViewerState(); |
| |
| if (fDocumentAdapter instanceof IDocumentAdapterExtension) { |
| IDocumentAdapterExtension extension= (IDocumentAdapterExtension) fDocumentAdapter; |
| extension.stopForwardingDocumentChanges(); |
| } |
| |
| if (fTextWidget != null && !fTextWidget.isDisposed()) |
| fTextWidget.setRedraw(false); |
| |
| fireRedrawChanged(); |
| } |
| |
| @Override |
| public final void setRedraw(boolean redraw) { |
| setRedraw(redraw, -1); |
| } |
| |
| /** |
| * Basically same functionality as <code>ITextViewerExtension.setRedraw(boolean)</code>. Adds a |
| * way for subclasses to pass in a desired top index that should be used when |
| * <code>redraw</code> is <code>true</code>. If <code>topIndex</code> is -1, this method is |
| * identical to <code>ITextViewerExtension.setRedraw(boolean)</code>. |
| * |
| * @see ITextViewerExtension#setRedraw(boolean) |
| * |
| * @param redraw <code>true</code> if redraw is enabled |
| * @param topIndex the top index |
| * @since 3.0 |
| */ |
| protected final void setRedraw(boolean redraw, int topIndex) { |
| if (!redraw) { |
| |
| ++ fRedrawCounter; |
| if (fRedrawCounter == 1) |
| disableRedrawing(); |
| |
| } else { |
| -- fRedrawCounter; |
| if (fRedrawCounter == 0) { |
| if (topIndex == -1) |
| enabledRedrawing(); |
| else |
| enabledRedrawing(topIndex); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether this viewer redraws itself. |
| * |
| * @return <code>true</code> if this viewer redraws itself |
| * @since 2.0 |
| */ |
| protected final boolean redraws() { |
| return fRedrawCounter <= 0; |
| } |
| |
| /** |
| * Starts the sequential rewrite mode of the viewer's document. |
| * |
| * @param normalized <code>true</code> if the rewrite is performed from the start to the end of the document |
| * @since 2.0 |
| * @deprecated since 3.1 use {@link IDocumentExtension4#startRewriteSession(DocumentRewriteSessionType)} instead |
| */ |
| @Deprecated |
| protected final void startSequentialRewriteMode(boolean normalized) { |
| IDocument document= getDocument(); |
| if (document instanceof IDocumentExtension) { |
| IDocumentExtension extension= (IDocumentExtension) document; |
| extension.startSequentialRewrite(normalized); |
| } |
| } |
| |
| /** |
| * Sets the sequential rewrite mode of the viewer's document. |
| * |
| * @since 2.0 |
| * @deprecated since 3.1 use {@link IDocumentExtension4#stopRewriteSession(DocumentRewriteSession)} instead |
| */ |
| @Deprecated |
| protected final void stopSequentialRewriteMode() { |
| IDocument document= getDocument(); |
| if (document instanceof IDocumentExtension) { |
| IDocumentExtension extension= (IDocumentExtension) document; |
| extension.stopSequentialRewrite(); |
| } |
| } |
| |
| @Override |
| public IRewriteTarget getRewriteTarget() { |
| if (fRewriteTarget == null) |
| fRewriteTarget= new RewriteTarget(); |
| return fRewriteTarget; |
| } |
| |
| @Override |
| public ITextHover getCurrentTextHover() { |
| if (fTextHoverManager == null) |
| return null; |
| return fTextHoverManager.getCurrentTextHover(); |
| } |
| |
| @Override |
| public Point getHoverEventLocation() { |
| if (fTextHoverManager == null) |
| return null; |
| return fTextHoverManager.getHoverEventLocation(); |
| } |
| |
| /** |
| * Returns the paint manager of this viewer. |
| * |
| * @return the paint manager of this viewer |
| * @since 2.1 |
| */ |
| protected PaintManager getPaintManager() { |
| if (fPaintManager == null) |
| fPaintManager= new PaintManager(this); |
| return fPaintManager; |
| } |
| |
| /** |
| * Adds the given painter to this viewer. If the painter is already registered |
| * this method is without effect. |
| * |
| * @param painter the painter to be added |
| * @since 2.1 |
| */ |
| @Override |
| public void addPainter(IPainter painter) { |
| getPaintManager().addPainter(painter); |
| } |
| |
| /** |
| * Removes the given painter from this viewer. If the painter has previously not been |
| * added to this viewer this method is without effect. |
| * |
| * @param painter the painter to be removed |
| * @since 2.1 |
| */ |
| @Override |
| public void removePainter(IPainter painter) { |
| getPaintManager().removePainter(painter); |
| } |
| |
| // ----------------------------------- conversions ------------------------------------------------------- |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#modelLine2WidgetLine(int)}. |
| * |
| * @param modelLine the model line |
| * @return the corresponding widget line or <code>-1</code> |
| * @since 2.1 |
| */ |
| public int modelLine2WidgetLine(int modelLine) { |
| if (fInformationMapping == null) |
| return modelLine; |
| |
| try { |
| return fInformationMapping.toImageLine(modelLine); |
| } catch (BadLocationException x) { |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#modelOffset2WidgetOffset(int)}. |
| * |
| * @param modelOffset the model offset |
| * @return the corresponding widget offset or <code>-1</code> |
| * @since 2.1 |
| */ |
| public int modelOffset2WidgetOffset(int modelOffset) { |
| if (fInformationMapping == null) |
| return modelOffset; |
| |
| try { |
| return fInformationMapping.toImageOffset(modelOffset); |
| } catch (BadLocationException x) { |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#modelRange2WidgetRange(IRegion)}. |
| * |
| * @param modelRange the model range |
| * @return the corresponding widget range or <code>null</code> |
| * @since 2.1 |
| */ |
| public IRegion modelRange2WidgetRange(IRegion modelRange) { |
| if (fInformationMapping == null) |
| return modelRange; |
| |
| try { |
| |
| if (modelRange.getLength() < 0) { |
| Region reversed= new Region(modelRange.getOffset() + modelRange.getLength(), -modelRange.getLength()); |
| IRegion result= fInformationMapping.toImageRegion(reversed); |
| if (result != null) |
| return new Region(result.getOffset() + result.getLength(), -result.getLength()); |
| } |
| return fInformationMapping.toImageRegion(modelRange); |
| |
| } catch (BadLocationException x) { |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Similar to {@link #modelRange2WidgetRange(IRegion)}, but more forgiving: |
| * if <code>modelRange</code> describes a region entirely hidden in the |
| * image, then this method returns the zero-length region at the offset of |
| * the folded region. |
| * |
| * @param modelRange the model range |
| * @return the corresponding widget range, or <code>null</code> |
| * @since 3.1 |
| */ |
| protected IRegion modelRange2ClosestWidgetRange(IRegion modelRange) { |
| if (!(fInformationMapping instanceof IDocumentInformationMappingExtension2)) |
| return modelRange2WidgetRange(modelRange); |
| |
| try { |
| if (modelRange.getLength() < 0) { |
| Region reversed= new Region(modelRange.getOffset() + modelRange.getLength(), -modelRange.getLength()); |
| IRegion result= ((IDocumentInformationMappingExtension2) fInformationMapping).toClosestImageRegion(reversed); |
| if (result != null) |
| return new Region(result.getOffset() + result.getLength(), -result.getLength()); |
| } |
| return ((IDocumentInformationMappingExtension2) fInformationMapping).toClosestImageRegion(modelRange); |
| |
| } catch (BadLocationException x) { |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#widgetLine2ModelLine(int)}. |
| * |
| * @param widgetLine the widget line |
| * @return the corresponding model line |
| * @since 2.1 |
| */ |
| public int widgetlLine2ModelLine(int widgetLine) { |
| return widgetLine2ModelLine(widgetLine); |
| } |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#widgetLine2ModelLine(int)}. |
| * |
| * @param widgetLine the widget line |
| * @return the corresponding model line or <code>-1</code> |
| * @since 3.0 |
| */ |
| public int widgetLine2ModelLine(int widgetLine) { |
| if (fInformationMapping == null) |
| return widgetLine; |
| |
| try { |
| return fInformationMapping.toOriginLine(widgetLine); |
| } catch (BadLocationException x) { |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#widgetOffset2ModelOffset(int)}. |
| * |
| * @param widgetOffset the widget offset |
| * @return the corresponding model offset or <code>-1</code> |
| * @since 2.1 |
| */ |
| public int widgetOffset2ModelOffset(int widgetOffset) { |
| if (fInformationMapping == null) |
| return widgetOffset; |
| |
| try { |
| return fInformationMapping.toOriginOffset(widgetOffset); |
| } catch (BadLocationException x) { |
| if (widgetOffset == getVisibleDocument().getLength()) { |
| IRegion coverage= fInformationMapping.getCoverage(); |
| return coverage.getOffset() + coverage.getLength(); |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#widgetRange2ModelRange(IRegion)}. |
| * |
| * @param widgetRange the widget range |
| * @return the corresponding model range or <code>null</code> |
| * @since 2.1 |
| */ |
| public IRegion widgetRange2ModelRange(IRegion widgetRange) { |
| if (fInformationMapping == null) |
| return widgetRange; |
| |
| try { |
| |
| if (widgetRange.getLength() < 0) { |
| Region reveresed= new Region(widgetRange.getOffset() + widgetRange.getLength(), -widgetRange.getLength()); |
| IRegion result= fInformationMapping.toOriginRegion(reveresed); |
| return new Region(result.getOffset() + result.getLength(), -result.getLength()); |
| } |
| |
| return fInformationMapping.toOriginRegion(widgetRange); |
| |
| } catch (BadLocationException x) { |
| int modelOffset= widgetOffset2ModelOffset(widgetRange.getOffset()); |
| if (modelOffset > -1) { |
| int modelEndOffset= widgetOffset2ModelOffset(widgetRange.getOffset() + widgetRange.getLength()); |
| if (modelEndOffset > -1) |
| return new Region(modelOffset, modelEndOffset - modelOffset); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#getModelCoverage()}. |
| * |
| * @return the model coverage |
| * @since 2.1 |
| */ |
| public IRegion getModelCoverage() { |
| if (fInformationMapping == null) { |
| IDocument document= getDocument(); |
| if (document == null) |
| return null; |
| return new Region(0, document.getLength()); |
| } |
| |
| return fInformationMapping.getCoverage(); |
| } |
| |
| /** |
| * Returns the line of the widget whose corresponding line in the viewer's document |
| * is closest to the given line in the viewer's document or <code>-1</code>. |
| * |
| * @param modelLine the line in the viewer's document |
| * @return the line in the widget that corresponds best to the given line in the viewer's document or <code>-1</code> |
| * @since 2.1 |
| */ |
| protected int getClosestWidgetLineForModelLine(int modelLine) { |
| if (fInformationMapping == null) |
| return modelLine; |
| |
| try { |
| return fInformationMapping.toClosestImageLine(modelLine); |
| } catch (BadLocationException x) { |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Translates a style range given relative to the viewer's document into style |
| * ranges relative to the viewer's widget or <code>null</code>. |
| * |
| * @param range the style range in the coordinates of the viewer's document |
| * @return the style range in the coordinates of the viewer's widget or <code>null</code> |
| * @since 2.1 |
| */ |
| protected StyleRange modelStyleRange2WidgetStyleRange(StyleRange range) { |
| IRegion region= modelRange2WidgetRange(new Region(range.start, range.length)); |
| if (region != null) { |
| StyleRange result= (StyleRange) range.clone(); |
| result.start= region.getOffset(); |
| result.length= region.getLength(); |
| return result; |
| } |
| return null; |
| } |
| |
| /** |
| * Same as {@link #modelRange2WidgetRange(IRegion)} just for a {@link org.eclipse.jface.text.Position}. |
| * |
| * @param modelPosition the position describing a range in the viewer's document |
| * @return a region describing a range in the viewer's widget |
| * @since 2.1 |
| */ |
| protected IRegion modelRange2WidgetRange(Position modelPosition) { |
| return modelRange2WidgetRange(new Region(modelPosition.getOffset(), modelPosition.getLength())); |
| } |
| |
| /** |
| * Translates the widget region of the given verify event into |
| * the corresponding region of the viewer's document. |
| * |
| * @param event the verify event |
| * @return the region of the viewer's document corresponding to the verify event |
| * @since 2.1 |
| */ |
| protected IRegion event2ModelRange(VerifyEvent event) { |
| |
| Region region= null; |
| if (event.start <= event.end) |
| region= new Region(event.start, event.end - event.start); |
| else |
| region= new Region(event.end, event.start - event.end); |
| |
| return widgetRange2ModelRange(region); |
| } |
| |
| /** |
| * Translates the given widget selection into the corresponding region |
| * of the viewer's document or returns <code>null</code> if this fails. |
| * |
| * @param widgetSelection the widget selection |
| * @return the region of the viewer's document corresponding to the widget selection or <code>null</code> |
| * @since 2.1 |
| */ |
| protected Point widgetSelection2ModelSelection(Point widgetSelection) { |
| IRegion region= new Region(widgetSelection.x, widgetSelection.y); |
| region= widgetRange2ModelRange(region); |
| return region == null ? null : new Point(region.getOffset(), region.getLength()); |
| } |
| |
| /** |
| * Translates the given selection range of the viewer's document into |
| * the corresponding widget range or returns <code>null</code> of this fails. |
| * |
| * @param modelSelection the selection range of the viewer's document |
| * @return the widget range corresponding to the selection range or <code>null</code> |
| * @since 2.1 |
| */ |
| protected Point modelSelection2WidgetSelection(Point modelSelection) { |
| if (fInformationMapping == null) |
| return modelSelection; |
| |
| try { |
| IRegion region= new Region(modelSelection.x, modelSelection.y); |
| region= fInformationMapping.toImageRegion(region); |
| if (region != null) |
| return new Point(region.getOffset(), region.getLength()); |
| } catch (BadLocationException x) { |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Implements the contract of {@link ITextViewerExtension5#widgetLineOfWidgetOffset(int)}. |
| * |
| * @param widgetOffset the widget offset |
| * @return the corresponding widget line or <code>-1</code> |
| * @since 2.1 |
| */ |
| public int widgetLineOfWidgetOffset(int widgetOffset) { |
| IDocument document= getVisibleDocument(); |
| if (document != null) { |
| try { |
| return document.getLineOfOffset(widgetOffset); |
| } catch (BadLocationException e) { |
| } |
| } |
| return -1; |
| } |
| |
| @Override |
| public boolean moveFocusToWidgetToken() { |
| if (fWidgetTokenKeeper instanceof IWidgetTokenKeeperExtension) { |
| IWidgetTokenKeeperExtension extension= (IWidgetTokenKeeperExtension) fWidgetTokenKeeper; |
| return extension.setFocus(this); |
| } |
| return false; |
| } |
| |
| /** |
| * Sets the document partitioning of this viewer. The partitioning is used by this viewer to |
| * access partitioning information of the viewers input document. |
| * |
| * @param partitioning the partitioning name |
| * @since 3.0 |
| */ |
| public void setDocumentPartitioning(String partitioning) { |
| fPartitioning= partitioning; |
| } |
| |
| /** |
| * Returns the document partitioning for this viewer. |
| * |
| * @return the document partitioning for this viewer |
| * @since 3.0 |
| */ |
| protected String getDocumentPartitioning() { |
| return fPartitioning; |
| } |
| |
| //---- Text presentation listeners ---- |
| |
| @Override |
| public void addTextPresentationListener(ITextPresentationListener listener) { |
| |
| Assert.isNotNull(listener); |
| |
| if (fTextPresentationListeners == null) |
| fTextPresentationListeners= new ArrayList<>(); |
| |
| if (!fTextPresentationListeners.contains(listener)) |
| fTextPresentationListeners.add(listener); |
| } |
| |
| @Override |
| public void removeTextPresentationListener(ITextPresentationListener listener) { |
| |
| Assert.isNotNull(listener); |
| |
| if (fTextPresentationListeners != null) { |
| fTextPresentationListeners.remove(listener); |
| if (fTextPresentationListeners.size() == 0) |
| fTextPresentationListeners= null; |
| } |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.IEditingSupportRegistry#registerHelper(org.eclipse.jface.text.IEditingSupport) |
| * @since 3.1 |
| */ |
| @Override |
| public void register(IEditingSupport helper) { |
| Assert.isLegal(helper != null); |
| fEditorHelpers.add(helper); |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.IEditingSupportRegistry#deregisterHelper(org.eclipse.jface.text.IEditingSupport) |
| * @since 3.1 |
| */ |
| @Override |
| public void unregister(IEditingSupport helper) { |
| fEditorHelpers.remove(helper); |
| } |
| |
| /* |
| * @see org.eclipse.jface.text.IEditingSupportRegistry#getCurrentHelpers() |
| * @since 3.1 |
| */ |
| @Override |
| public IEditingSupport[] getRegisteredSupports() { |
| return fEditorHelpers.toArray(new IEditingSupport[fEditorHelpers.size()]); |
| } |
| |
| @Override |
| public void setHyperlinkDetectors(IHyperlinkDetector[] hyperlinkDetectors, int eventStateMask) { |
| if (fHyperlinkDetectors != null) { |
| for (IHyperlinkDetector fHyperlinkDetector : fHyperlinkDetectors) { |
| if (fHyperlinkDetector instanceof IHyperlinkDetectorExtension) |
| ((IHyperlinkDetectorExtension)fHyperlinkDetector).dispose(); |
| } |
| } |
| |
| boolean enable= hyperlinkDetectors != null && hyperlinkDetectors.length > 0; |
| fHyperlinkStateMask= eventStateMask; |
| fHyperlinkDetectors= hyperlinkDetectors; |
| if (enable) { |
| if (fHyperlinkManager != null) { |
| fHyperlinkManager.setHyperlinkDetectors(fHyperlinkDetectors); |
| fHyperlinkManager.setHyperlinkStateMask(fHyperlinkStateMask); |
| } |
| ensureHyperlinkManagerInstalled(); |
| } else { |
| if (fHyperlinkManager != null) |
| fHyperlinkManager.uninstall(); |
| fHyperlinkManager= null; |
| } |
| } |
| |
| /** |
| * Sets the hyperlink presenter. |
| * <p> |
| * This is only valid as long as the hyperlink manager hasn't |
| * been created yet. |
| * </p> |
| * |
| * @param hyperlinkPresenter the hyperlink presenter |
| * @throws IllegalStateException if the hyperlink manager has already been created |
| * @since 3.1 |
| */ |
| public void setHyperlinkPresenter(IHyperlinkPresenter hyperlinkPresenter) throws IllegalStateException { |
| if (fHyperlinkManager != null) |
| throw new IllegalStateException(); |
| |
| fHyperlinkPresenter= hyperlinkPresenter; |
| ensureHyperlinkManagerInstalled(); |
| } |
| |
| /** |
| * Ensures that the hyperlink manager has been |
| * installed if a hyperlink detector is available. |
| * |
| * @since 3.1 |
| */ |
| private void ensureHyperlinkManagerInstalled() { |
| if (fHyperlinkDetectors != null && fHyperlinkDetectors.length > 0 && fHyperlinkPresenter != null && fHyperlinkManager == null) { |
| DETECTION_STRATEGY strategy= fHyperlinkPresenter.canShowMultipleHyperlinks() ? HyperlinkManager.ALL : HyperlinkManager.FIRST; |
| fHyperlinkManager= new HyperlinkManager(strategy); |
| fHyperlinkManager.install(this, fHyperlinkPresenter, fHyperlinkDetectors, fHyperlinkStateMask); |
| } |
| } |
| |
| @Override |
| public void setTabsToSpacesConverter(IAutoEditStrategy converter) { |
| fTabsToSpacesConverter= converter; |
| } |
| |
| } |