| /* |
| * Copyright 2000-2011 JetBrains s.r.o. |
| * |
| * 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.intellij.execution.impl; |
| |
| import com.intellij.execution.filters.HyperlinkInfo; |
| import com.intellij.execution.ui.ConsoleViewContentType; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.List; |
| |
| import static com.intellij.execution.impl.ConsoleViewImpl.HyperlinkTokenInfo; |
| import static com.intellij.execution.impl.ConsoleViewImpl.TokenInfo; |
| |
| /** |
| * Holds utility methods for console processing. |
| * |
| * @author Denis Zhdanov |
| * @since 4/6/11 3:50 PM |
| */ |
| public class ConsoleUtil { |
| |
| private ConsoleUtil() { |
| } |
| |
| public static void addToken(int length, @Nullable HyperlinkInfo info, ConsoleViewContentType contentType, @NotNull List<TokenInfo> tokens) { |
| int startOffset = 0; |
| if (!tokens.isEmpty()) { |
| final TokenInfo lastToken = tokens.get(tokens.size() - 1); |
| if (lastToken.contentType == contentType && info == lastToken.getHyperlinkInfo()) { |
| lastToken.endOffset += length; // optimization |
| return; |
| } |
| else { |
| startOffset = lastToken.endOffset; |
| } |
| } |
| |
| tokens.add(info != null ? new HyperlinkTokenInfo(contentType, startOffset, startOffset + length, info) |
| : new TokenInfo(contentType, startOffset, startOffset + length)); |
| } |
| |
| /** |
| * Utility method that allows to adjust ranges of the given tokens within the text removal. |
| * <p/> |
| * <b>Note:</b> it's assumed that the given tokens list is ordered by offset in ascending order. |
| * |
| * @param tokens target tokens ordered by offset in ascending order |
| * @param startOffset start offset of the removed text (inclusive) |
| * @param endOffset end offset of the removed text (exclusive) |
| */ |
| public static void updateTokensOnTextRemoval(@NotNull List<? extends TokenInfo> tokens, int startOffset, int endOffset) { |
| final int firstIndex = findTokenInfoIndexByOffset(tokens, startOffset); |
| if (firstIndex >= tokens.size()) { |
| return; |
| } |
| |
| int removedSymbolsNumber = endOffset - startOffset; |
| boolean updateOnly = false; |
| int removeIndexStart = -1; |
| int removeIndexEnd = -1; |
| final TokenInfo firstToken = tokens.get(firstIndex); |
| |
| if (startOffset == firstToken.startOffset) { |
| // Removed range is located entirely at the first token. |
| if (endOffset < firstToken.endOffset) { |
| firstToken.endOffset -= removedSymbolsNumber; |
| updateOnly = true; |
| } |
| // The first token is completely removed. |
| else { |
| removeIndexStart = removeIndexEnd = firstIndex; |
| } |
| } |
| // Removed range is located entirely at the first token. |
| else if (endOffset <= firstToken.endOffset) { |
| firstToken.endOffset -= removedSymbolsNumber; |
| updateOnly = true; |
| } |
| |
| for (int i = firstIndex + 1; i < tokens.size(); i++) { |
| final TokenInfo tokenInfo = tokens.get(i); |
| if (updateOnly) { |
| tokenInfo.startOffset -= removedSymbolsNumber; |
| tokenInfo.endOffset -= removedSymbolsNumber; |
| continue; |
| } |
| |
| // We know that start offset lays before the current token, so, it's completely removed if end offset is not less than |
| // the token's end offset. |
| if (endOffset >= tokenInfo.endOffset) { |
| if (removeIndexStart < 0) { |
| removeIndexStart = i; |
| } |
| removeIndexEnd = i; |
| continue; |
| } |
| |
| // Update current token offsets and adjust ranges of all subsequent tokens. |
| tokenInfo.startOffset = startOffset; |
| tokenInfo.endOffset = startOffset + (tokenInfo.endOffset - endOffset); |
| updateOnly = true; |
| } |
| |
| if (removeIndexStart >= 0) { |
| tokens.subList(removeIndexStart, removeIndexEnd + 1).clear(); |
| } |
| } |
| |
| /** |
| * Searches given collection for the token that contains given offset. |
| * <p/> |
| * <b>Note:</b> it's assumed that the given tokens list is ordered by offset in ascending order. |
| * |
| * @param tokens target tokens ordered by offset in ascending order |
| * @param offset target offset |
| * @return index of the target token within the given list; given list length if no such token is found |
| */ |
| public static int findTokenInfoIndexByOffset(@NotNull List<? extends TokenInfo> tokens, final int offset) { |
| int low = 0; |
| int high = tokens.size() - 1; |
| |
| while (low <= high) { |
| final int mid = (low + high) / 2; |
| final TokenInfo midVal = tokens.get(mid); |
| if (offset < midVal.startOffset) { |
| high = mid - 1; |
| } |
| else if (offset >= midVal.endOffset) { |
| low = mid + 1; |
| } |
| else { |
| return mid; |
| } |
| } |
| return tokens.size(); |
| } |
| } |