* <p/>
* <b>Note:</b> basically this class consists of functionality that is cut from {@link ConsoleViewImpl} in order to make it possible
* to cover it by tests.
- *
+ *
* @author Denis Zhdanov
* @since 4/5/11 5:26 PM
*/
* <p/>
* Feel free to check rationale for using this approach at {@link #myCyclicBufferSize} contract.
*/
- private final Deque<StringBuilder> myDeferredOutput = new ArrayDeque<StringBuilder>();
+ private final Deque<StringBuilder> myDeferredOutput = new ArrayDeque<StringBuilder>();
private final Set<ConsoleViewContentType> myContentTypesToNotStripOnCycling = new HashSet<ConsoleViewContentType>();
-
+
/**
* Main console usage scenario assumes the following:
* <pre>
* to be moved. Current constant defines default size of that small buffers.
*/
- private final int myCyclicBufferSize;
- private final int myCyclicBufferUnitSize;
+ private final int myCyclicBufferSize;
+ private final int myCyclicBufferUnitSize;
private final boolean myUseCyclicBuffer;
/**
*/
private int myDeferredOutputLength;
- /** Buffer for deferred stdin and stderr output. */
+ /**
+ * Buffer for deferred stdin and stderr output.
+ */
private StringBuffer myDeferredUserInput = new StringBuffer();
/**
final String useCycleBufferProperty = System.getProperty("idea.cycle.buffer.size");
return useCycleBufferProperty == null || !"disabled".equalsIgnoreCase(useCycleBufferProperty);
}
-
+
private static int getCycleBufferSize() {
final String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size");
if (cycleBufferSizeProperty == null) return 1024 * 1024;
public boolean isEmpty() {
return myDeferredOutput.isEmpty() || (myDeferredOutput.size() == 1 && myDeferredOutput.getFirst().length() <= 0);
}
-
+
public int getLength() {
return myDeferredOutputLength;
}
return myDeferredOutput;
}
- public String getText() {
- if (myDeferredOutput.size() > 1) {
+ public StringBuilder[] getText() {
+ if (myDeferredOutput.size() > 0) {
final StringBuilder buffer = new StringBuilder();
for (StringBuilder builder : myDeferredOutput) {
buffer.append(builder);
}
- return buffer.toString();
- }
- else if (myDeferredOutput.size() == 1) {
- return myDeferredOutput.getFirst().substring(0);
+ return myDeferredOutput.toArray(new StringBuilder[myDeferredOutput.size()]);
}
else {
- return "";
+ return new StringBuilder[0];
}
}
* <p/>
* {@link ConsoleViewContentType#USER_INPUT} is considered to be such a type by default, however, it's possible to overwrite that
* via the current method.
- *
- * @param types content types that should not be stripped during the buffer's cycling
+ *
+ * @param types content types that should not be stripped during the buffer's cycling
*/
public void setContentTypesToNotStripOnCycling(@NotNull Collection<ConsoleViewContentType> types) {
myContentTypesToNotStripOnCycling.clear();
myContentTypesToNotStripOnCycling.addAll(types);
}
-
+
public void clear() {
if (myUseCyclicBuffer) {
myDeferredOutput.clear();
myDeferredUserInput = new StringBuffer();
myDeferredTokens.clear();
}
-
+
@Nullable
public String cutFirstUserInputLine() {
final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length());
public void replaceUserText(int startOffset, int endOffset, String text) {
myDeferredUserInput.replace(startOffset, endOffset, text);
}
-
+
/**
* Asks current buffer to store given text of the given type.
- *
- * @param s text to store
- * @param contentType type of the given text
- * @param info hyperlink info for the given text (if any)
- * @return text that is actually stored (there is a possible case that the buffer is full and given text's type
- * is considered to have lower priority than the stored one, hence, it's better to drop given text completely
- * or partially) and number of existed symbols removed during storing the given data
+ *
+ * @param s text to store
+ * @param contentType type of the given text
+ * @param info hyperlink info for the given text (if any)
+ * @return text that is actually stored (there is a possible case that the buffer is full and given text's type
+ * is considered to have lower priority than the stored one, hence, it's better to drop given text completely
+ * or partially) and number of existed symbols removed during storing the given data
*/
@NotNull
public Pair<String, Integer> print(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) {
myDeferredTypes.add(contentType);
s = StringUtil.convertLineSeparators(s, !SystemInfo.isMac);
-
+
myDeferredOutputLength += s.length();
StringBuilder bufferToUse;
if (myDeferredOutput.isEmpty()) {
if (bufferToUse.length() >= myCyclicBufferUnitSize) {
myDeferredOutput.add(bufferToUse = new StringBuilder(myCyclicBufferUnitSize));
}
-
+
if (bufferToUse.length() < myCyclicBufferUnitSize) {
int numberOfSymbolsToAdd = Math.min(myCyclicBufferUnitSize - bufferToUse.length(), s.length() - offset);
bufferToUse.append(s.substring(offset, offset + numberOfSymbolsToAdd));
if (contentType == ConsoleViewContentType.USER_INPUT) {
myDeferredUserInput.append(s);
}
-
+
ConsoleUtil.addToken(s.length(), info, contentType, myDeferredTokens);
return new Pair<String, Integer>(s, trimmedSymbolsNumber);
}
// }
// }
//}
-
+
/**
* IJ console works as follows - it receives managed process outputs from dedicated thread that serves that process and
* pushes it to the {@link Document document} of editor used to represent process console. Important point here is that process
* to push to the document then. Current method serves exactly that purpose, i.e. it's expected to be called when new chunk of
* text is received from the underlying process and trims existing text buffer if necessary.
*
- * @param numberOfNewSymbols number of symbols read from the managed process output
- * @return number of newly read symbols that should be accepted
+ * @param numberOfNewSymbols number of symbols read from the managed process output
+ * @return number of newly read symbols that should be accepted
*/
@SuppressWarnings({"ForLoopReplaceableByForEach"})
private int trimDeferredOutputIfNecessary(final int numberOfNewSymbols) {
}
Context context = new Context(numberOfSymbolsToRemove);
-
+
TIntArrayList indicesOfTokensToRemove = new TIntArrayList();
for (int i = 0; i < myDeferredTokens.size(); i++) {
TokenInfo tokenInfo = myDeferredTokens.get(i);
tokenInfo.startOffset -= context.removedSymbolsNumber;
tokenInfo.endOffset -= context.removedSymbolsNumber;
-
+
if (!context.canContinueProcessing()) {
// Just update token offsets.
myDeferredTypes.add(tokenInfo.contentType);
TokenInfo tokenInfo = myDeferredTokens.get(0);
if (tokenInfo.startOffset > 0) {
final HyperlinkInfo hyperlinkInfo = tokenInfo.getHyperlinkInfo();
- myDeferredTokens.add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo)
- : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset));
+ myDeferredTokens
+ .add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo)
+ : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset));
myDeferredTypes.add(ConsoleViewContentType.USER_INPUT);
}
}
}
}
}
-
+
private int remove(@NotNull Context context, int tokenLength) {
int removedSymbolsNumber = 0;
int remainingTotalNumberOfSymbolsToRemove = context.numberOfSymbolsToRemove - context.removedSymbolsNumber;
}
return removedSymbolsNumber;
}
-
+
private final class Context {
public final int numberOfSymbolsToRemove;
currentBuffer = null;
}
}
-
+
public boolean canContinueProcessing() {
return removedSymbolsNumber < numberOfSymbolsToRemove && currentBuffer != null;
}
-
+
public boolean nextBuffer() {
if (iterator.hasNext()) {
currentBuffer = iterator.next();
return false;
}
}
-
+
@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
- private void dumpDeferredOutput() {
+ private void dumpDeferredOutput() {
if (!DEBUG_PROCESSING) {
return;
}
}
log("-----------------------------------------------------------------------------------------------------");
}
-
+
@SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"})
private static void log(Object o) {
//try {
package com.intellij.execution.impl;
+import com.google.common.collect.Lists;
import com.intellij.codeInsight.navigation.IncrementalSearchHandler;
import com.intellij.execution.ConsoleFolding;
import com.intellij.execution.ExecutionBundle;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiDocumentManager;
import java.util.concurrent.CopyOnWriteArraySet;
public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator {
-
+
private @NonNls String CONSOLE_VIEW_POPUP_MENU = "ConsoleView.PopupMenu";
private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl");
private void printHyperlink(String s, ConsoleViewContentType contentType, HyperlinkInfo info) {
synchronized (LOCK) {
- Pair<String,Integer> pair = myBuffer.print(s, contentType, info);
+ Pair<String, Integer> pair = myBuffer.print(s, contentType, info);
s = pair.first;
myContentSize += s.length() - pair.second;
}
protected void beforeExternalAddContentToDocument(int length, ConsoleViewContentType contentType) {
- myContentSize+=length;
+ myContentSize += length;
addToken(length, null, contentType);
}
}, null, DocCommandGroupId.noneGroupId(document));
}
-
- final String text;
+
+ final StringBuilder[] text;
final Collection<ConsoleViewContentType> contentTypes;
int deferredTokensSize;
synchronized (LOCK) {
if (myEditor == null) return;
text = myBuffer.getText();
-
+
contentTypes = Collections.unmodifiableCollection(new HashSet<ConsoleViewContentType>(myBuffer.getDeferredTokenTypes()));
List<TokenInfo> deferredTokens = myBuffer.getDeferredTokens();
for (TokenInfo deferredToken : deferredTokens) {
final Document document = myEditor.getDocument();
final int oldLineCount = document.getLineCount();
final boolean isAtEndOfDocument = myEditor.getCaretModel().getOffset() == document.getTextLength();
- boolean cycleUsed = myBuffer.isUseCyclicBuffer() && document.getTextLength() + text.length() > myBuffer.getCyclicBufferSize();
+ int textLength = 0;
+
+ final List<String> textLines = splitToLines(text);
+ for (String line : textLines) {
+ textLength += line.length();
+ }
+
+ boolean cycleUsed = myBuffer.isUseCyclicBuffer() && document.getTextLength() + textLength > myBuffer.getCyclicBufferSize();
CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
public void run() {
int offset = myEditor.getCaretModel().getOffset();
myEditor.getScrollingModel().accumulateViewportChanges();
}
try {
- String[] strings = text.split("\\r");
- for (int i = 0; i < strings.length - 1; i++) {
- document.insertString(document.getTextLength(), strings[i]);
+
+ for (int i = 0; i < textLines.size() - 1; i++) {
+ document.insertString(document.getTextLength(), textLines.get(i));
int lastLine = document.getLineCount() - 1;
if (lastLine >= 0) {
document.deleteString(document.getLineStartOffset(lastLine), document.getTextLength());
}
}
- document.insertString(document.getTextLength(), strings[strings.length - 1]);
+ document.insertString(document.getTextLength(), textLines.get(textLines.size() - 1));
}
finally {
if (preserveCurrentVisualArea) {
}
}, null, DocCommandGroupId.noneGroupId(document));
synchronized (LOCK) {
- for (int i = myTokens.size() - 1; i >=0 && deferredTokensSize > 0; i--, deferredTokensSize--) {
+ for (int i = myTokens.size() - 1; i >= 0 && deferredTokensSize > 0; i--, deferredTokensSize--) {
TokenInfo token = myTokens.get(i);
final HyperlinkInfo info = token.getHyperlinkInfo();
if (info != null) {
myPsiDisposedCheck.performCheck();
final int newLineCount = document.getLineCount();
if (cycleUsed) {
- final int lineCount = LineTokenizer.calcLineCount(text, true);
+ final int lineCount = textLines.size();
for (Iterator<RangeHighlighter> it = myHyperlinks.getRanges().keySet().iterator(); it.hasNext();) {
if (!it.next().isValid()) {
it.remove();
}
}
+ private List<String> splitToLines(StringBuilder[] text) {
+ final List<String> textLines = Lists.newArrayList();
+ StringBuilder line = new StringBuilder();
+ for (StringBuilder textItem : text) {
+ for (int j = 0; j < textItem.length(); j++) {
+ if (textItem.charAt(j) == '\r') {
+ textLines.add(new String(line));
+ line.setLength(0);
+ }
+ else {
+ line.append(textItem.charAt(j));
+ }
+ }
+ }
+ if (line.length() > 0) {
+ textLines.add(line.toString());
+ }
+
+ if (textLines.size() == 0) {
+ textLines.add("");
+ }
+ return textLines;
+ }
+
private void flushDeferredUserInput() {
final String textToSend = myBuffer.cutFirstUserInputLine();
if (textToSend == null) {
@Override
public Color getDefaultBackground() {
final Color color = getColor(ConsoleViewContentType.CONSOLE_BACKGROUND_KEY);
- return color == null? super.getDefaultBackground() : color;
+ return color == null ? super.getDefaultBackground() : color;
}
};
editor.setColorsScheme(scheme);
scrollTo(next.getStartOffset());
}
final HyperlinkInfo hyperlinkInfo = myHyperlinks.getRanges().get(next);
- return hyperlinkInfo == null ? null : new OccurenceInfo(new Navigatable.Adapter() {
+ return hyperlinkInfo == null ? null : new OccurenceInfo(new Navigatable.Adapter() {
public void navigate(final boolean requestFocus) {
hyperlinkInfo.navigate(myProject);
linkFollowed(hyperlinkInfo);
* used soft wraps mode and perform update if we see that the current value differs from the stored.
*/
private boolean myLastIsSelected;
-
+
@Override
protected Editor getEditor(AnActionEvent e) {
return myEditor;