void (@NotNull String text, @NotNull ProcessOutputType outputType, @NotNull Runnable flushRunnable) { long nowNano = getNanoTime(); synchronized (myLock) { ProcessOutputType baseOutputType = outputType.getBaseOutputType(); if (ProcessOutputType.SYSTEM.equals(baseOutputType)) { handleSystemOutput(flushRunnable, nowNano); return; } Boolean textEndsWithNewline = text.isEmpty() ? null : StringUtil.endsWithChar(text, '\n'); if ((myLastFlushedChunkBaseOutputType == null || baseOutputType.equals(myLastFlushedChunkBaseOutputType))) { boolean newlineAdded = !myFlushedChunksEndWithNewline && Boolean.TRUE.equals(textEndsWithNewline); if (textEndsWithNewline != null) { myFlushedChunksEndWithNewline = textEndsWithNewline; } myLastFlushedChunkBaseOutputType = baseOutputType; myLastFlushedChunkCreatedNanoTime = nowNano; flushRunnable.run(); if (newlineAdded && myPendingChunks.size() > 0 && myPendingChunks.get(0).getNanoTimePassedSinceLastFlushedChunk(nowNano) >= AWAIT_SAME_STREAM_TEXT_NANO) { processPendingChunks(nowNano); } return; } Chunk chunk = new Chunk(textEndsWithNewline, baseOutputType, nowNano, myLastFlushedChunkCreatedNanoTime, flushRunnable); myPendingChunks.add(chunk); if (!isProcessingScheduled()) { long delayNano = AWAIT_SAME_STREAM_TEXT_NANO - chunk.getNanoTimePassedSinceLastFlushedChunk(nowNano); if (delayNano <= 0) { processPendingChunks(nowNano); } else { myReschedules++; scheduleProcessPendingChunks(delayNano); } } } }