| /* |
| * Copyright 2000-2012 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 org.jetbrains.idea.devkit.actions; |
| |
| import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfo; |
| import com.intellij.codeInsight.daemon.impl.IndentsPass; |
| import com.intellij.lang.annotation.HighlightSeverity; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.actionSystem.LangDataKeys; |
| import com.intellij.openapi.actionSystem.PlatformDataKeys; |
| import com.intellij.openapi.application.AccessToken; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessorEx; |
| import com.intellij.openapi.command.UndoConfirmationPolicy; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.SelectionModel; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.util.Processor; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author gregsh |
| */ |
| public class ToggleHighlightingMarkupAction extends AnAction { |
| @Override |
| public void update(AnActionEvent e) { |
| Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); |
| PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); |
| e.getPresentation().setEnabled(editor != null && file != null); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); |
| PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); |
| if (editor == null || file == null) return; |
| final Project project = file.getProject(); |
| CommandProcessorEx commandProcessor = (CommandProcessorEx)CommandProcessorEx.getInstance(); |
| Object commandToken = commandProcessor.startCommand(project, e.getPresentation().getText(), e.getPresentation().getText(), UndoConfirmationPolicy.DEFAULT); |
| AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(getClass()); |
| try { |
| final SelectionModel selectionModel = editor.getSelectionModel(); |
| int[] starts = selectionModel.getBlockSelectionStarts(); |
| int[] ends = selectionModel.getBlockSelectionEnds(); |
| |
| int startOffset = starts.length == 0? 0 : starts[0]; |
| int endOffset = ends.length == 0? editor.getDocument().getTextLength() : ends[ends.length - 1]; |
| |
| perform(project, editor.getDocument(), startOffset, endOffset); |
| } |
| finally { |
| token.finish(); |
| commandProcessor.finishCommand(project, commandToken, null); |
| } |
| } |
| |
| private static void perform(Project project, final Document document, final int startOffset, final int endOffset) { |
| final CharSequence sequence = document.getCharsSequence(); |
| final StringBuilder sb = new StringBuilder(); |
| Pattern pattern = Pattern.compile("<(error|warning|EOLError|EOLWarning|info|weak_warning)((?:\\s|=|\\w+|\\\"(?:[^\"]|\\\\\\\")*?\\\")*?)>(.*?)</\\1>"); |
| Matcher matcher = pattern.matcher(sequence); |
| List<TextRange> ranges = new ArrayList<TextRange>(); |
| if (matcher.find(startOffset)) { |
| boolean compactMode = false; |
| int pos; |
| do { |
| if (matcher.start(0) >= endOffset) break; |
| if (matcher.start(2) < matcher.end(2)) { |
| if (!compactMode) { |
| ranges.clear(); |
| compactMode = true; |
| } |
| ranges.add(new TextRange(matcher.start(2), matcher.end(2))); |
| } |
| else if (!compactMode) { |
| ranges.add(new TextRange(matcher.start(0), matcher.start(3))); |
| ranges.add(new TextRange(matcher.end(3), matcher.end(0))); |
| } |
| pos = Math.max(matcher.end(1), matcher.end(2)); |
| } |
| while (matcher.find(pos)); |
| Collections.sort(ranges, IndentsPass.RANGE_COMPARATOR); |
| } |
| if (!ranges.isEmpty()) { |
| int pos = 0; |
| for (TextRange range : ranges) { |
| sb.append(sequence, pos, range.getStartOffset()); |
| pos = range.getEndOffset(); |
| } |
| sb.append(sequence, pos, sequence.length()); |
| } |
| else { |
| final int[] offset = new int[] {0}; |
| final ArrayList<HighlightInfo> infos = new ArrayList<HighlightInfo>(); |
| DaemonCodeAnalyzerEx.processHighlights( |
| document, project, HighlightSeverity.WARNING, 0, sequence.length(), |
| new Processor<HighlightInfo>() { |
| @Override |
| public boolean process(HighlightInfo info) { |
| if (info.getSeverity() != HighlightSeverity.WARNING && info.getSeverity() != HighlightSeverity.ERROR) return true; |
| if (info.getStartOffset() >= endOffset) return false; |
| if (info.getEndOffset() > startOffset) { |
| offset[0] = appendInfo(info, sb, sequence, offset[0], infos, false); |
| } |
| return true; |
| } |
| }); |
| offset[0] = appendInfo(null, sb, sequence, offset[0], infos, false); |
| sb.append(sequence.subSequence(offset[0], sequence.length())); |
| } |
| document.setText(sb); |
| } |
| |
| private static int appendInfo(@Nullable HighlightInfo info, |
| StringBuilder sb, |
| CharSequence sequence, |
| int offset, |
| ArrayList<HighlightInfo> infos, final boolean compact) { |
| if (info == null || !infos.isEmpty() && getMaxEnd(infos) < info.getStartOffset()) { |
| if (infos.size() == 1) { |
| HighlightInfo cur = infos.remove(0); |
| sb.append(sequence.subSequence(offset, cur.getStartOffset())); |
| appendTag(sb, cur, true, compact); |
| sb.append(sequence.subSequence(cur.getStartOffset(), cur.getEndOffset())); |
| appendTag(sb, cur, false, compact); |
| offset = cur.getEndOffset(); |
| } |
| else { |
| // process overlapped |
| LinkedList<HighlightInfo> stack = new LinkedList<HighlightInfo>(); |
| for (HighlightInfo cur : infos) { |
| offset = processStack(stack, sb, sequence, offset, cur.getStartOffset(), compact); |
| sb.append(sequence.subSequence(offset, cur.getStartOffset())); |
| offset = cur.getStartOffset(); |
| appendTag(sb, cur, true, compact); |
| stack.addLast(cur); |
| } |
| offset = processStack(stack, sb, sequence, offset, sequence.length(), compact); |
| infos.clear(); |
| } |
| } |
| if (info != null) { |
| boolean found = false; |
| for (HighlightInfo cur : infos) { |
| if (cur.getStartOffset() == info.getStartOffset() && cur.getEndOffset() == info.getEndOffset() && cur.getSeverity() == |
| info.getSeverity()) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) infos.add(info); |
| } |
| return offset; |
| } |
| |
| private static int getMaxEnd(ArrayList<HighlightInfo> infos) { |
| int max = -1; |
| for (HighlightInfo info : infos) { |
| int endOffset = info.getEndOffset(); |
| if (max < endOffset) max = endOffset; |
| } |
| return max; |
| } |
| |
| private static int processStack(LinkedList<HighlightInfo> stack, |
| StringBuilder sb, |
| CharSequence sequence, |
| int offset, |
| final int endOffset, |
| final boolean compact) { |
| if (stack.isEmpty()) return offset; |
| for (HighlightInfo cur = stack.peekLast(); cur != null && cur.getEndOffset() <= endOffset; cur = stack.peekLast()) { |
| stack.removeLast(); |
| if (offset <= cur.getEndOffset()) { |
| sb.append(sequence.subSequence(offset, cur.getEndOffset())); |
| } |
| else { |
| //System.out.println("Incorrect overlapping infos: " + offset + " > " + cur.getEndOffset()); |
| } |
| offset = cur.getEndOffset(); |
| appendTag(sb, cur, false, compact); |
| } |
| return offset; |
| } |
| |
| private static void appendTag(StringBuilder sb, HighlightInfo cur, boolean opening, final boolean compact) { |
| sb.append("<"); |
| if (!opening) sb.append("/"); |
| if (cur.isAfterEndOfLine()) { |
| sb.append(cur.getSeverity() == HighlightSeverity.WARNING ? "EOLWarning" : "EOLError"); |
| } |
| else { |
| sb.append(cur.getSeverity() == HighlightSeverity.WARNING ? "warning" : "error"); |
| } |
| if (opening && !compact) { |
| sb.append(" descr=\"").append(cur.getDescription()).append("\""); |
| |
| } |
| sb.append(">"); |
| } |
| } |