/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/PreparedFragmentedContent.java

https://bitbucket.org/nbargnesi/idea · Java · 372 lines · 301 code · 47 blank · 24 comment · 35 complexity · f9000443b453e48a1825170996063155 MD5 · raw file

  1. /*
  2. * Copyright 2000-2011 JetBrains s.r.o.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.intellij.openapi.vcs.changes;
  17. import com.intellij.openapi.application.ApplicationManager;
  18. import com.intellij.openapi.diff.DiffContent;
  19. import com.intellij.openapi.diff.SimpleContent;
  20. import com.intellij.openapi.editor.Document;
  21. import com.intellij.openapi.editor.colors.EditorColorsManager;
  22. import com.intellij.openapi.editor.highlighter.*;
  23. import com.intellij.openapi.editor.markup.TextAttributes;
  24. import com.intellij.openapi.fileTypes.FileType;
  25. import com.intellij.openapi.fileTypes.SyntaxHighlighter;
  26. import com.intellij.openapi.project.DumbService;
  27. import com.intellij.openapi.project.Project;
  28. import com.intellij.openapi.util.Getter;
  29. import com.intellij.openapi.util.Pair;
  30. import com.intellij.openapi.util.TextRange;
  31. import com.intellij.openapi.vcs.FilePath;
  32. import com.intellij.openapi.vcs.ProjectLevelVcsManager;
  33. import com.intellij.openapi.vcs.VcsConfiguration;
  34. import com.intellij.openapi.vcs.history.VcsRevisionNumber;
  35. import com.intellij.openapi.vcs.impl.ContentRevisionCache;
  36. import com.intellij.openapi.vfs.VirtualFile;
  37. import com.intellij.util.BeforeAfter;
  38. import com.intellij.util.Consumer;
  39. import com.intellij.util.continuation.ModalityIgnorantBackgroundableTask;
  40. import java.util.ArrayList;
  41. import java.util.Collections;
  42. import java.util.List;
  43. import java.util.ListIterator;
  44. /**
  45. * Created by IntelliJ IDEA.
  46. * User: Irina.Chernushina
  47. * Date: 9/8/11
  48. * Time: 1:19 PM
  49. */
  50. public class PreparedFragmentedContent {
  51. private LineNumberConvertor oldConvertor;
  52. private LineNumberConvertor newConvertor;
  53. private StringBuilder sbOld;
  54. private StringBuilder sbNew;
  55. private List<TextRange> myBeforeFragments;
  56. private List<TextRange> myAfterFragments;
  57. private List<BeforeAfter<Integer>> myLineRanges;
  58. private boolean myOneSide;
  59. private boolean myIsAddition;
  60. private FragmentedEditorHighlighter myBeforeHighlighter;
  61. private FragmentedEditorHighlighter myAfterHighlighter;
  62. private List<Pair<TextRange, TextAttributes>> myBeforeTodoRanges;
  63. private List<Pair<TextRange, TextAttributes>> myAfterTodoRanges;
  64. private final Project myProject;
  65. private final FragmentedContent myFragmentedContent;
  66. private final String myFileName;
  67. private final FileType myFileType;
  68. private final VcsRevisionNumber myBeforeNumber;
  69. private final VcsRevisionNumber myAfterNumber;
  70. private VirtualFile myFile;
  71. private FilePath myFilePath;
  72. public PreparedFragmentedContent(final Project project, final FragmentedContent fragmentedContent, final String fileName,
  73. final FileType fileType,
  74. VcsRevisionNumber beforeNumber,
  75. VcsRevisionNumber afterNumber,
  76. FilePath path,
  77. VirtualFile file) {
  78. myFile = file;
  79. myProject = project;
  80. myFragmentedContent = fragmentedContent;
  81. myFileName = fileName;
  82. myFileType = fileType;
  83. myBeforeNumber = beforeNumber;
  84. myAfterNumber = afterNumber;
  85. myFilePath = path;
  86. oldConvertor = new LineNumberConvertor();
  87. newConvertor = new LineNumberConvertor();
  88. sbOld = new StringBuilder();
  89. sbNew = new StringBuilder();
  90. myBeforeFragments = new ArrayList<TextRange>(fragmentedContent.getSize());
  91. myAfterFragments = new ArrayList<TextRange>(fragmentedContent.getSize());
  92. myLineRanges = new ArrayList<BeforeAfter<Integer>>();
  93. fromFragmentedContent(fragmentedContent);
  94. }
  95. public void recalculate() {
  96. oldConvertor = new LineNumberConvertor();
  97. newConvertor = new LineNumberConvertor();
  98. sbOld = new StringBuilder();
  99. sbNew = new StringBuilder();
  100. myBeforeFragments = new ArrayList<TextRange>(myFragmentedContent.getSize());
  101. myAfterFragments = new ArrayList<TextRange>(myFragmentedContent.getSize());
  102. myLineRanges = new ArrayList<BeforeAfter<Integer>>();
  103. checkFileOutdated();
  104. fromFragmentedContent(myFragmentedContent);
  105. }
  106. private void checkFileOutdated() {
  107. if (myOneSide) {
  108. if (myIsAddition) {
  109. if (myFile == null || ! myFile.isValid()) {
  110. throw new ChangeOutdatedException();
  111. }
  112. }
  113. } else {
  114. if (myFile == null || ! myFile.isValid()) {
  115. throw new ChangeOutdatedException();
  116. }
  117. }
  118. }
  119. private void fromFragmentedContent(final FragmentedContent fragmentedContent) {
  120. ApplicationManager.getApplication().runReadAction(new Runnable() { // todo
  121. @Override
  122. public void run() {
  123. if (DumbService.isDumb(myProject)) {
  124. throw new ModalityIgnorantBackgroundableTask.ToBeRepeatedException();
  125. }
  126. myOneSide = fragmentedContent.isOneSide();
  127. myIsAddition = fragmentedContent.isAddition();
  128. List<BeforeAfter<TextRange>> expandedRanges =
  129. expand(fragmentedContent.getRanges(), VcsConfiguration.getInstance(myProject).SHORT_DIFF_EXTRA_LINES,
  130. fragmentedContent.getBefore(), fragmentedContent.getAfter());
  131. // add "artificial" empty lines
  132. // line starts
  133. BeforeAfter<Integer> lines = new BeforeAfter<Integer>(0, 0);
  134. for (BeforeAfter<TextRange> lineNumbers : expandedRanges) {
  135. if (lines.getBefore() > 0 || lines.getAfter() > 0) {
  136. oldConvertor.emptyLine(lines.getBefore());
  137. newConvertor.emptyLine(lines.getAfter());
  138. lines = new BeforeAfter<Integer>(lines.getBefore() + 1, lines.getAfter() + 1);
  139. sbOld.append('\n');
  140. sbNew.append('\n');
  141. }
  142. myLineRanges.add(lines);
  143. oldConvertor.put(lines.getBefore(), lineNumbers.getBefore().getStartOffset());
  144. newConvertor.put(lines.getAfter(), lineNumbers.getAfter().getStartOffset());
  145. final Document document = fragmentedContent.getBefore();
  146. if (sbOld.length() > 0) {
  147. sbOld.append('\n');
  148. }
  149. final TextRange beforeRange = new TextRange(document.getLineStartOffset(lineNumbers.getBefore().getStartOffset()),
  150. document.getLineEndOffset(lineNumbers.getBefore().getEndOffset()));
  151. myBeforeFragments.add(beforeRange);
  152. sbOld.append(document.getText(beforeRange));
  153. final Document document1 = fragmentedContent.getAfter();
  154. if (sbNew.length() > 0) {
  155. sbNew.append('\n');
  156. }
  157. final TextRange afterRange = new TextRange(document1.getLineStartOffset(lineNumbers.getAfter().getStartOffset()),
  158. document1.getLineEndOffset(lineNumbers.getAfter().getEndOffset()));
  159. myAfterFragments.add(afterRange);
  160. sbNew.append(document1.getText(afterRange));
  161. int before = lines.getBefore() + lineNumbers.getBefore().getEndOffset() - lineNumbers.getBefore().getStartOffset() + 1;
  162. int after = lines.getAfter() + lineNumbers.getAfter().getEndOffset() - lineNumbers.getAfter().getStartOffset() + 1;
  163. lines = new BeforeAfter<Integer>(before, after);
  164. }
  165. myLineRanges.add(new BeforeAfter<Integer>(lines.getBefore() == 0 ? 0 : lines.getBefore() - 1,
  166. lines.getAfter() == 0 ? 0 : lines.getAfter() - 1));
  167. setHighlighters(fragmentedContent.getBefore(), fragmentedContent.getAfter(), expandedRanges);
  168. setTodoHighlighting(fragmentedContent.getBefore(), fragmentedContent.getAfter());
  169. }
  170. });
  171. }
  172. public LineNumberConvertor getOldConvertor() {
  173. return oldConvertor;
  174. }
  175. public LineNumberConvertor getNewConvertor() {
  176. return newConvertor;
  177. }
  178. public DiffContent createBeforeContent() {
  179. if (isAddition()) {
  180. return SimpleContent.createEmpty();
  181. }
  182. return new SimpleContent(getSbOld().toString());
  183. }
  184. public DiffContent createAfterContent() {
  185. if (isDeletion()) {
  186. return SimpleContent.createEmpty();
  187. }
  188. return new SimpleContent(getSbNew().toString());
  189. }
  190. public StringBuilder getSbOld() {
  191. return sbOld;
  192. }
  193. public StringBuilder getSbNew() {
  194. return sbNew;
  195. }
  196. public List<TextRange> getBeforeFragments() {
  197. return myBeforeFragments;
  198. }
  199. public List<TextRange> getAfterFragments() {
  200. return myAfterFragments;
  201. }
  202. public List<BeforeAfter<Integer>> getLineRanges() {
  203. return myLineRanges;
  204. }
  205. public boolean isOneSide() {
  206. return myOneSide;
  207. }
  208. public boolean isAddition() {
  209. return myOneSide && myIsAddition;
  210. }
  211. public boolean isDeletion() {
  212. return myOneSide && ! myIsAddition;
  213. }
  214. public FragmentedEditorHighlighter getBeforeHighlighter() {
  215. return myBeforeHighlighter;
  216. }
  217. public void setBeforeHighlighter(FragmentedEditorHighlighter beforeHighlighter) {
  218. myBeforeHighlighter = beforeHighlighter;
  219. }
  220. public FragmentedEditorHighlighter getAfterHighlighter() {
  221. return myAfterHighlighter;
  222. }
  223. public void setAfterHighlighter(FragmentedEditorHighlighter afterHighlighter) {
  224. myAfterHighlighter = afterHighlighter;
  225. }
  226. public boolean isEmpty() {
  227. return myLineRanges.isEmpty();
  228. }
  229. public void setAfterTodoRanges(List<Pair<TextRange, TextAttributes>> afterTodoRanges) {
  230. myAfterTodoRanges = afterTodoRanges;
  231. }
  232. public List<Pair<TextRange, TextAttributes>> getBeforeTodoRanges() {
  233. return myBeforeTodoRanges;
  234. }
  235. public List<Pair<TextRange, TextAttributes>> getAfterTodoRanges() {
  236. return myAfterTodoRanges;
  237. }
  238. public void setBeforeTodoRanges(List<Pair<TextRange, TextAttributes>> beforeTodoRanges) {
  239. myBeforeTodoRanges = beforeTodoRanges;
  240. }
  241. public static List<BeforeAfter<TextRange>> expand(List<BeforeAfter<TextRange>> myRanges, final int lines, final Document oldDocument,
  242. final Document document) {
  243. if (myRanges == null || myRanges.isEmpty()) return Collections.emptyList();
  244. if (lines == -1) {
  245. final List<BeforeAfter<TextRange>> shiftedRanges = new ArrayList<BeforeAfter<TextRange>>(1);
  246. shiftedRanges.add(new BeforeAfter<TextRange>(new TextRange(0, oldDocument.getLineCount() == 0 ? 0 : oldDocument.getLineCount() - 1),
  247. new TextRange(0, document.getLineCount() == 0 ? 0 : document.getLineCount() - 1)));
  248. return shiftedRanges;
  249. }
  250. final List<BeforeAfter<TextRange>> shiftedRanges = new ArrayList<BeforeAfter<TextRange>>(myRanges.size());
  251. final int oldLineCount = oldDocument.getLineCount();
  252. final int lineCount = document.getLineCount();
  253. for (BeforeAfter<TextRange> range : myRanges) {
  254. final TextRange newBefore = expandRange(range.getBefore(), lines, oldLineCount);
  255. final TextRange newAfter = expandRange(range.getAfter(), lines, lineCount);
  256. shiftedRanges.add(new BeforeAfter<TextRange>(newBefore, newAfter));
  257. }
  258. // and zip
  259. final List<BeforeAfter<TextRange>> zippedRanges = new ArrayList<BeforeAfter<TextRange>>(myRanges.size());
  260. final ListIterator<BeforeAfter<TextRange>> iterator = shiftedRanges.listIterator();
  261. BeforeAfter<TextRange> previous = iterator.next();
  262. while (iterator.hasNext()) {
  263. final BeforeAfter<TextRange> current = iterator.next();
  264. if (neighbourOrIntersect(previous.getBefore(), current.getBefore()) ||
  265. neighbourOrIntersect(previous.getAfter(), current.getAfter())) {
  266. previous = new BeforeAfter<TextRange>(previous.getBefore().union(current.getBefore()),
  267. previous.getAfter().union(current.getAfter()));
  268. } else {
  269. zippedRanges.add(previous);
  270. previous = current;
  271. }
  272. }
  273. zippedRanges.add(previous);
  274. return zippedRanges;
  275. }
  276. private static boolean neighbourOrIntersect(final TextRange a, final TextRange b) {
  277. return a.getEndOffset() + 1 == b.getStartOffset() || a.intersects(b);
  278. }
  279. private static TextRange expandRange(final TextRange range, final int shift, final int size) {
  280. return new TextRange(Math.max(0, range.getStartOffset() - shift), Math.max(0, Math.min(size - 1, range.getEndOffset() + shift)));
  281. }
  282. private void setHighlighters(final Document oldDocument, final Document document,
  283. List<BeforeAfter<TextRange>> ranges) {
  284. EditorHighlighterFactory editorHighlighterFactory = EditorHighlighterFactory.getInstance();
  285. final SyntaxHighlighter syntaxHighlighter = SyntaxHighlighter.PROVIDER.create(myFileType, myProject, null);
  286. final EditorHighlighter highlighter =
  287. editorHighlighterFactory.createEditorHighlighter(syntaxHighlighter, EditorColorsManager.getInstance().getGlobalScheme());
  288. highlighter.setEditor(new LightHighlighterClient(oldDocument, myProject));
  289. highlighter.setText(oldDocument.getText());
  290. HighlighterIterator iterator = highlighter.createIterator(ranges.get(0).getBefore().getStartOffset());
  291. FragmentedEditorHighlighter beforeHighlighter =
  292. new FragmentedEditorHighlighter(iterator, getBeforeFragments(), 1, true);
  293. setBeforeHighlighter(beforeHighlighter);
  294. final EditorHighlighter highlighter1 =
  295. editorHighlighterFactory.createEditorHighlighter(syntaxHighlighter, EditorColorsManager.getInstance().getGlobalScheme());
  296. highlighter1.setEditor(new LightHighlighterClient(document, myProject));
  297. highlighter1.setText(document.getText());
  298. HighlighterIterator iterator1 = highlighter1.createIterator(ranges.get(0).getAfter().getStartOffset());
  299. FragmentedEditorHighlighter afterHighlighter =
  300. new FragmentedEditorHighlighter(iterator1, getAfterFragments(), 1, true);
  301. setAfterHighlighter(afterHighlighter);
  302. }
  303. private void setTodoHighlighting(final Document oldDocument, final Document document) {
  304. final ContentRevisionCache cache = ProjectLevelVcsManager.getInstance(myProject).getContentRevisionCache();
  305. final List<Pair<TextRange,TextAttributes>> beforeTodoRanges = myBeforeNumber == null ? Collections.<Pair<TextRange,TextAttributes>>emptyList() :
  306. new TodoForBaseRevision(myProject, getBeforeFragments(), 1, myFileName, oldDocument.getText(), true, myFileType, new Getter<Object>() {
  307. @Override
  308. public Object get() {
  309. return cache.getCustom(myFilePath, myBeforeNumber);
  310. }
  311. }, new Consumer<Object>() {
  312. @Override
  313. public void consume(Object items) {
  314. cache.putCustom(myFilePath, myBeforeNumber, items);
  315. }
  316. }).execute();
  317. final List<Pair<TextRange, TextAttributes>> afterTodoRanges = new TodoForExistingFile(myProject, getAfterFragments(), 1,
  318. myFileName, document.getText(), false, myFileType, myFile).execute();
  319. setBeforeTodoRanges(beforeTodoRanges);
  320. setAfterTodoRanges(afterTodoRanges);
  321. }
  322. public VirtualFile getFile() {
  323. return myFile;
  324. }
  325. }