Java tutorial
/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tools.idea.welcome.wizard; import com.android.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.highlighter.HighlighterClient; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** * Keeps track of attributes for ranges and has some special support for tracking process output. */ public final class ConsoleHighlighter extends DocumentAdapter implements EditorHighlighter { private List<HighlightRange> myRanges = Lists.newArrayListWithCapacity(1024); private boolean myIsUpdatePending = false; private StringBuilder myPendingStrings = new StringBuilder(4096); private HighlighterClient myEditor; private ModalityState myModalityState = ModalityState.defaultModalityState(); public synchronized void print(String string, @Nullable TextAttributes attributes) { Application application = ApplicationManager.getApplication(); myPendingStrings.append(string); if (!myIsUpdatePending && application != null && !application.isUnitTestMode()) { myIsUpdatePending = true; application.invokeLater(new Runnable() { @Override public void run() { appendToDocument(); } }, myModalityState); } HighlightRange lastRange = Iterables.getLast(myRanges, HighlightRange.EMPTY); assert lastRange != null; int start = lastRange.end; myRanges.add(new HighlightRange(start, start + string.length(), attributes)); } public void setModalityState(ModalityState state) { myModalityState = state; } /** * Code that requires locking and will be executed on UI thread. We should * not do long-running UI operations (e.g. document append) under the lock. */ private synchronized String getPendingString() { String string = myPendingStrings.toString(); myPendingStrings.delete(0, string.length()); myIsUpdatePending = false; return string; } private void appendToDocument() { Document document = myEditor.getDocument(); if (document != null) { String pendingString = StringUtil.convertLineSeparators(getPendingString()); document.insertString(document.getTextLength(), pendingString); if (myEditor instanceof Editor) { Editor editor = (Editor) myEditor; int lineCount = document.getLineCount(); editor.getScrollingModel().scrollTo(new LogicalPosition(lineCount - 1, 0), ScrollType.MAKE_VISIBLE); } } } @NotNull @Override public HighlighterIterator createIterator(int startOffset) { return new HighlightedRangesIterator(getOffsetRangeIndex(startOffset)); } @Override public void setText(@NotNull CharSequence text) { } @Override public void setEditor(@NotNull HighlighterClient editor) { myEditor = editor; } @Override public void setColorScheme(@NotNull EditorColorsScheme scheme) { } private synchronized HighlightRange getRange(int index) { if (index < 0 || index >= myRanges.size()) { return HighlightRange.EMPTY; } else { return myRanges.get(index); } } @VisibleForTesting synchronized int getOffsetRangeIndex(int startOffset) { if (myRanges.isEmpty() || startOffset < 0 || startOffset >= Iterables.getLast(myRanges).end) { return -1; } int end = myRanges.size(); int i = end / 2; while (true) { HighlightRange range = myRanges.get(i); if (range.end > startOffset) { if (range.start <= startOffset) { return i; } else { end = i; i /= 2; } } else { i = (i + end) / 2; } } } public void clear() { clearHighlightedState(); myEditor.getDocument().setText(""); } private synchronized void clearHighlightedState() { myRanges.clear(); myPendingStrings.delete(0, myPendingStrings.length() - 1); myIsUpdatePending = false; } public void attachToProcess(ProcessHandler processHandler) { processHandler.addProcessListener(new ProcessOutputProcessor()); } private static final class HighlightRange { public final static HighlightRange EMPTY = new HighlightRange(0, 0, null); @Nullable public final TextAttributes attributes; public final int start; public final int end; public HighlightRange(int start, int end, @Nullable TextAttributes attributes) { this.attributes = attributes; this.start = start; this.end = end; } } private class ProcessOutputProcessor extends ProcessAdapter { private AtomicBoolean mySkipped = new AtomicBoolean(false); @Override public void onTextAvailable(final ProcessEvent event, final Key outputType) { if (!mySkipped.compareAndSet(false, true)) { print(event.getText(), ConsoleViewContentType.getConsoleViewType(outputType).getAttributes()); } } } private class HighlightedRangesIterator implements HighlighterIterator { private int myIndex; @NotNull private HighlightRange myRange = HighlightRange.EMPTY; public HighlightedRangesIterator(int index) { myIndex = index; myRange = getRange(index); } @Nullable @Override public TextAttributes getTextAttributes() { return myRange.attributes; } @Override public int getStart() { return myRange.start; } @Override public int getEnd() { return myRange.end; } @Nullable @Override public IElementType getTokenType() { return null; } @Override public void advance() { myRange = getRange(++myIndex); } @Override public void retreat() { myRange = getRange(--myIndex); } @Override public boolean atEnd() { return myRange == HighlightRange.EMPTY; } @Override public Document getDocument() { return myEditor.getDocument(); } } }