1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.atlassian.theplugin.idea.ui.linkhiglighter;
17
18 import com.atlassian.theplugin.idea.IdeaHelper;
19 import com.intellij.openapi.editor.Document;
20 import com.intellij.openapi.editor.Editor;
21 import com.intellij.openapi.editor.event.DocumentAdapter;
22 import com.intellij.openapi.editor.event.DocumentEvent;
23 import com.intellij.openapi.editor.markup.RangeHighlighter;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.psi.PsiFile;
27 import org.jetbrains.annotations.NotNull;
28
29 import java.util.ArrayList;
30 import java.util.List;
31
32
33
34
35 public class JiraLinkHighlighter {
36 private final Project project;
37 private final VirtualFile newFile;
38 private final PsiFile psiFile;
39 private final Editor editor;
40 private final JiraEditorLinkParser jiraEditorLinkParser;
41 private final List<JiraURLTextRange> ranges = new ArrayList<JiraURLTextRange>();
42 private EditorInputHandler inputEditorInputHandler = null;
43 private DocumentAdapter docAdapter = null;
44 private boolean isListening = false;
45
46 public JiraLinkHighlighter(@NotNull final Project project, final VirtualFile newFile,
47 final PsiFile psiFile,
48 final Editor editor, JiraEditorLinkParser jiraEditorLinkParser) {
49 this.project = project;
50
51
52 this.newFile = newFile;
53 this.psiFile = psiFile;
54 this.editor = editor;
55 this.jiraEditorLinkParser = jiraEditorLinkParser;
56 }
57
58 public void stopListening() {
59 if (isListening) {
60 editor.removeEditorMouseListener(inputEditorInputHandler);
61 editor.removeEditorMouseMotionListener(inputEditorInputHandler);
62 editor.getContentComponent().removeKeyListener(inputEditorInputHandler);
63 editor.getDocument().removeDocumentListener(docAdapter);
64 isListening = false;
65 }
66 }
67
68 public void removeAllRanges() {
69 for (JiraURLTextRange range : ranges) {
70 range.setActive(false);
71 }
72 highlightLink(ranges);
73 ranges.clear();
74 }
75
76 public void startListeninig() {
77 listenOnDocument();
78 listenOnInput();
79 isListening = true;
80 }
81
82 public void reparseAll() {
83 int length = editor.getDocument().getCharsSequence().length();
84 parseDocumentRanges(0, length, length);
85 }
86
87
88 public void checkComments() {
89 List<JiraURLTextRange> newRanges = new ArrayList<JiraURLTextRange>();
90 for (JiraURLTextRange range : ranges) {
91 boolean isComment = JiraEditorLinkParser.isComment(psiFile, range.getStartOffset());
92 if (isComment != range.isActive()) {
93 range.setActive(isComment);
94 newRanges.add(range);
95 }
96 }
97
98 if (!newRanges.isEmpty()) {
99 highlightLink(newRanges);
100 }
101
102 }
103
104 private void listenOnInput() {
105 inputEditorInputHandler = new EditorInputHandler(IdeaHelper.getProjectCfgManager(project), project, editor, psiFile,
106 jiraEditorLinkParser);
107 editor.getContentComponent().addKeyListener(inputEditorInputHandler);
108 editor.addEditorMouseMotionListener(inputEditorInputHandler);
109 editor.addEditorMouseListener(inputEditorInputHandler);
110 }
111
112 private void listenOnDocument() {
113 final Document doc = editor.getDocument();
114 docAdapter = new DocumentAdapter() {
115
116 public void beforeDocumentChange(final DocumentEvent event) {
117 if (event.getNewLength() < event.getOldLength()) {
118 forgetDocumentRanges(event.getOffset(), event.getOffset() + event.getOldLength());
119 }
120 super.beforeDocumentChange(event);
121 }
122
123 public void documentChanged(final DocumentEvent event) {
124 parseDocumentRanges(event.getOffset(), event.getOldLength(), event.getNewLength());
125 super.documentChanged(event);
126 }
127 };
128
129 doc.addDocumentListener(docAdapter);
130
131
132 }
133
134 private void parseDocumentRanges(final int offset, final int oldLength, final int newLength) {
135 List<JiraURLTextRange> newRanges = jiraEditorLinkParser.getJiraURLTextRange(editor, getStartLineOffset(editor, offset),
136 getEndLineOffset(editor, offset + newLength));
137
138 List<JiraURLTextRange> rangesToForget = new ArrayList<JiraURLTextRange>();
139 List<JiraURLTextRange> intersectingRanges = getIntersectingRanges(offset, oldLength);
140
141 if (newLength != oldLength) {
142
143 for (JiraURLTextRange range : ranges) {
144 if (range.getStartOffset() >= offset) {
145 range.shift(newLength - oldLength);
146 }
147 }
148 }
149
150 for (JiraURLTextRange irange : intersectingRanges) {
151 boolean oldInNew = false;
152 for (JiraURLTextRange newRange : newRanges) {
153 if (irange.equals(newRange)) {
154 oldInNew = true;
155 break;
156 }
157 }
158
159 if (!oldInNew) {
160 rangesToForget.add(irange);
161 }
162 }
163
164 if (!rangesToForget.isEmpty()) {
165 for (JiraURLTextRange range : rangesToForget) {
166 range.setActive(false);
167 ranges.remove(range);
168 }
169 highlightLink(rangesToForget);
170 }
171
172 if (!newRanges.isEmpty()) {
173 List<JiraURLTextRange> rangesToRemember = new ArrayList<JiraURLTextRange>();
174 for (JiraURLTextRange range : newRanges) {
175 if (!ranges.contains(range)) {
176 rangesToRemember.add(range);
177 }
178 }
179
180 if (!rangesToRemember.isEmpty()) {
181 ranges.addAll(rangesToRemember);
182 highlightLink(ranges);
183
184 }
185 }
186
187 }
188
189 private List<JiraURLTextRange> getIntersectingRanges(final int offset, final int oldLength) {
190 List<JiraURLTextRange> intersecting = new ArrayList<JiraURLTextRange>();
191 for (JiraURLTextRange range : ranges) {
192 if (Math.max(range.getStartOffset(), offset) <= Math.min(range.getEndOffset(), offset + oldLength)) {
193 intersecting.add(range);
194 }
195 }
196
197 return intersecting;
198 }
199
200 public static int getEndLineOffset(final Editor editor, final int o) {
201 final int textLength = editor.getDocument().getTextLength();
202 int offset = Math.max(Math.min(o, Math.max(0, textLength)), Math.min(0, textLength));
203 int lineCount = editor.getDocument().getLineCount();
204 int lineNumber = 0;
205
206 if (offset < 0) {
207 lineNumber = 0;
208 } else if (offset < textLength) {
209 lineNumber = editor.getDocument().getLineNumber(offset);
210 } else {
211 lineNumber = lineCount - 1;
212 }
213
214 if (lineNumber >= 0 && lineNumber < lineCount) {
215 return editor.getDocument().getLineEndOffset(lineNumber);
216 } else {
217 return 0;
218 }
219 }
220
221 private void highlightLink(final List<JiraURLTextRange> rangesList) {
222 for (RangeHighlighter h : editor.getMarkupModel().getAllHighlighters()) {
223 JiraURLTextRange jiraRange = JiraURLTextRange.getFrom(h);
224 if (jiraRange != null && rangesList.contains(jiraRange) && !jiraRange.isActive()) {
225 editor.getMarkupModel().removeHighlighter(h);
226
227 }
228 }
229
230 for (JiraURLTextRange urlTextRange : rangesList) {
231 if (urlTextRange.isActive()) {
232 urlTextRange.addLinkHighlighter(editor);
233 }
234 }
235 }
236
237 public static int getStartLineOffset(final Editor editor, final int o) {
238 final int textLength = editor.getDocument().getTextLength();
239 int offset = Math.max(Math.min(o, Math.max(0, textLength)), Math.min(0, textLength));
240 int lineCount = editor.getDocument().getLineCount();
241 int lineNumber = 0;
242
243 if (offset < 0) {
244 lineNumber = 0;
245 } else if (offset < textLength) {
246 lineNumber = editor.getDocument().getLineNumber(offset);
247 } else {
248 lineNumber = lineCount - 1;
249 }
250
251 if (lineNumber >= 0 && lineNumber < lineCount) {
252 return editor.getDocument().getLineStartOffset(lineNumber);
253 } else {
254 return 0;
255 }
256 }
257
258 private void forgetDocumentRanges(final int start, final int end) {
259 List<JiraURLTextRange> forgetRangesList = new ArrayList<JiraURLTextRange>();
260 for (JiraURLTextRange urlRange : ranges) {
261 if (Math.max(start, urlRange.getStartOffset()) < Math.min(end, urlRange.getEndOffset())) {
262 forgetRangesList.add(urlRange);
263 urlRange.setActive(false);
264 }
265
266 if (!forgetRangesList.isEmpty()) {
267 highlightLink(forgetRangesList);
268 }
269
270 }
271
272 if (!forgetRangesList.isEmpty()) {
273 ranges.removeAll(forgetRangesList);
274 }
275
276 }
277 }