/* * Copyright 2000-2016 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.openapi.vcs; import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.SimpleModificationTracker; import com.intellij.openapi.util.TextRange; import com.intellij.util.io.URLUtil; import com.intellij.util.xmlb.XmlSerializerUtil; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author yole */ @State(name = "IssueNavigationConfiguration", storages = @Storage("vcs.xml")) public class IssueNavigationConfiguration extends SimpleModificationTracker implements PersistentStateComponent<IssueNavigationConfiguration> { private static final Logger LOG = Logger.getInstance(IssueNavigationConfiguration.class); public static IssueNavigationConfiguration getInstance(Project project) { return PeriodicalTasksCloser.getInstance().safeGetService(project, IssueNavigationConfiguration.class); } private List<IssueNavigationLink> myLinks = new ArrayList<>(); public List<IssueNavigationLink> getLinks() { return myLinks; } public void setLinks(final List<IssueNavigationLink> links) { myLinks = new ArrayList<>(links); incModificationCount(); } public IssueNavigationConfiguration getState() { return this; } public void loadState(IssueNavigationConfiguration state) { XmlSerializerUtil.copyBean(state, this); } public static class LinkMatch implements Comparable { private final TextRange myRange; private final String myTargetUrl; public LinkMatch(final TextRange range, final String targetUrl) { myRange = range; myTargetUrl = targetUrl; } public TextRange getRange() { return myRange; } public String getTargetUrl() { return myTargetUrl; } public int compareTo(Object o) { if (!(o instanceof LinkMatch)) { return 0; } return myRange.getStartOffset() - ((LinkMatch)o).getRange().getStartOffset(); } } public List<LinkMatch> findIssueLinks(CharSequence text) { final List<LinkMatch> result = new ArrayList<>(); try { for (IssueNavigationLink link : myLinks) { Pattern issuePattern = link.getIssuePattern(); Matcher m = issuePattern.matcher(text); while (m.find()) { try { String replacement = issuePattern.matcher(m.group(0)).replaceFirst(link.getLinkRegexp()); addMatch(result, new TextRange(m.start(), m.end()), replacement); } catch (Exception e) { LOG.debug("Malformed regex replacement. IssueLink: " + link + "; text: " + text, e); } } } Matcher m = URLUtil.URL_PATTERN.matcher(text); while (m.find()) { addMatch(result, new TextRange(m.start(), m.end()), m.group()); } } catch (ProcessCanceledException e) { //skip too long processing completely } Collections.sort(result); return result; } private static void addMatch(final List<LinkMatch> result, final TextRange range, final String replacement) { for (Iterator<LinkMatch> iterator = result.iterator(); iterator.hasNext(); ) { LinkMatch oldMatch = iterator.next(); if (range.contains(oldMatch.getRange())) { iterator.remove(); } else if (oldMatch.getRange().contains(range)) { return; } } result.add(new LinkMatch(range, replacement)); } }