blob: 7d977066ab9ed431d79f63bca9ff9d554986a025 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 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
* vogella GmbH - Bug 287303 - [patch] Add Word Wrap action to Console View
* Paul Pazderski - Bug 550621 - improved verification of user input
*******************************************************************************/
package org.eclipse.ui.internal.console;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.MultiStringMatcher;
import org.eclipse.jface.text.MultiStringMatcher.Match;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.StyledTextContent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsoleDocumentPartitioner;
import org.eclipse.ui.console.IConsoleDocumentPartitionerExtension;
import org.eclipse.ui.console.IOConsole;
import org.eclipse.ui.console.IScrollLockStateProvider;
import org.eclipse.ui.console.TextConsole;
import org.eclipse.ui.console.TextConsoleViewer;
/**
* Viewer used to display an {@link IOConsole}.
*
* @since 3.1
*/
public class IOConsoleViewer extends TextConsoleViewer {
/**
* Will always scroll with output if value is true.
*/
private boolean fAutoScroll = true;
/**
* Listener required for auto scroll.
*/
private IDocumentListener fAutoScrollListener;
/**
* Matcher to find line delimiters which are used by current document.
* <code>null</code> if no document is set.
*/
private MultiStringMatcher lineDelimiterMatcher;
/**
* Constructs a new viewer in the given parent for the specified console.
*
* @param parent the containing composite
* @param console the IO console
*/
public IOConsoleViewer(Composite parent, TextConsole console) {
super(parent, console);
}
/**
* Constructs a new viewer in the given parent for the specified console.
*
* @param parent the containing composite
* @param console the IO console
* @param scrollLockStateProvider the scroll lock state provider
* @since 3.6
*/
public IOConsoleViewer(Composite parent, TextConsole console, IScrollLockStateProvider scrollLockStateProvider) {
super(parent, console, scrollLockStateProvider);
}
public boolean isAutoScroll() {
return fAutoScroll;
}
public void setAutoScroll(boolean scroll) {
fAutoScroll = scroll;
}
public boolean isWordWrap() {
return getTextWidget().getWordWrap();
}
public void setWordWrap(boolean wordwrap) {
getTextWidget().setWordWrap(wordwrap);
}
@Override
protected void handleVerifyEvent(VerifyEvent e) {
final IConsoleDocumentPartitioner partitioner = (IConsoleDocumentPartitioner) getDocument()
.getDocumentPartitioner();
if (partitioner == null) {
e.doit = false;
return;
}
final IConsoleDocumentPartitionerExtension partitionerExt = (IConsoleDocumentPartitionerExtension) partitioner;
final StyledTextContent content = getTextWidget().getContent();
final String eventText = e.text != null ? e.text : ""; //$NON-NLS-1$
final Match newlineMatch = lineDelimiterMatcher != null ? lineDelimiterMatcher.indexOf(eventText, 0) : null;
final IRegion eventRange = event2ModelRange(e);
final int offset = eventRange.getOffset();
final int length = eventRange.getLength();
if (length > 0 && partitionerExt.containsReadOnly(offset, length)) {
// If user tries to remove or replace text range containing read-only content we
// modify the change to only remove the writable parts.
e.doit = false;
final ITypedRegion[] writableParts = partitionerExt.computeWritablePartitions(offset, length);
// process text removes in reveres to not bother with changing offsets
for (int i = writableParts.length - 1; i >= 0; i--) {
final ITypedRegion writablePart = writableParts[i];
int replaceOffset = writablePart.getOffset();
int replaceLength = writablePart.getLength();
// snap partitions to event range
final int underflow = offset - writablePart.getOffset();
if (underflow > 0) {
replaceOffset += underflow;
replaceLength -= underflow;
}
final int overflow = (replaceOffset + replaceLength) - (offset + length);
if (overflow > 0) {
replaceLength -= overflow;
}
content.replaceTextRange(replaceOffset, replaceLength, ""); //$NON-NLS-1$
}
// now add the users input if any
if (eventText.length() > 0) {
getTextWidget().replaceTextRange(offset, 0, eventText);
}
} else if (newlineMatch != null && offset != content.getCharCount()) {
// If newline is entered within a line this viewer will not break that line and
// instead pretend as if newline was entered at end of document.
e.doit = false;
if (newlineMatch.getOffset() > 0) {
// insert text until newline with further verification
// and newline plus trailing text without
getTextWidget().replaceTextRange(offset, length, eventText.substring(0, newlineMatch.getOffset()));
content.replaceTextRange(content.getCharCount(), 0,
eventText.substring(newlineMatch.getOffset(), eventText.length()));
} else {
// inserted text starts with newline
content.replaceTextRange(content.getCharCount(), 0, eventText);
}
getTextWidget().setCaretOffset(content.getCharCount());
getTextWidget().showSelection();
} else if (partitioner.isReadOnly(offset) && partitioner.isReadOnly(offset - 1)) {
// If input is entered in read-only partition add it to the next writable
// partition instead
e.doit = false;
final int insertOffset = partitionerExt.getNextOffsetByState(offset, true);
content.replaceTextRange(insertOffset, 0, eventText);
getTextWidget().setCaretOffset(insertOffset + eventText.length());
getTextWidget().showSelection();
} else {
super.handleVerifyEvent(e);
}
}
/**
* Makes the associated text widget uneditable.
*/
public void setReadOnly() {
ConsolePlugin.getStandardDisplay().asyncExec(() -> {
StyledText text = getTextWidget();
if (text != null && !text.isDisposed()) {
text.setEditable(false);
}
});
}
/**
* @return <code>false</code> if text is editable
*/
public boolean isReadOnly() {
return !getTextWidget().getEditable();
}
@Override
public void setDocument(IDocument document) {
if (getDocument() != null) {
getDocument().removeDocumentListener(getAutoScrollListener());
}
super.setDocument(document);
lineDelimiterMatcher = null;
if (document != null) {
lineDelimiterMatcher = MultiStringMatcher.create(document.getLegalLineDelimiters());
document.addDocumentListener(getAutoScrollListener());
}
}
/**
* Must create listener dynamically since super constructor may call
* {@link #setDocument(IDocument)} before field initialization.
*
* @return document listener to perform auto scroll
*/
private IDocumentListener getAutoScrollListener() {
if (fAutoScrollListener == null) {
fAutoScrollListener = new IDocumentListener() {
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
}
@Override
public void documentChanged(DocumentEvent event) {
if (fAutoScroll) {
revealEndOfDocument();
}
}
};
}
return fAutoScrollListener;
}
}