01615020dd139c0d28464fb61aaa723d7f696a97384c46b3c7597d8d1ff601e40dd02ef1fcc3e1d22eefb76527e9443bdd4a7845672d567e220e5cdc9ea9e0 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import * as assert from '../../../base/common/assert.js';
  6. import { Emitter } from '../../../base/common/event.js';
  7. import { Disposable } from '../../../base/common/lifecycle.js';
  8. import * as objects from '../../../base/common/objects.js';
  9. import { Range } from '../../common/core/range.js';
  10. const defaultOptions = {
  11. followsCaret: true,
  12. ignoreCharChanges: true,
  13. alwaysRevealFirst: true
  14. };
  15. /**
  16. * Create a new diff navigator for the provided diff editor.
  17. */
  18. export class DiffNavigator extends Disposable {
  19. constructor(editor, options = {}) {
  20. super();
  21. this._onDidUpdate = this._register(new Emitter());
  22. this._editor = editor;
  23. this._options = objects.mixin(options, defaultOptions, false);
  24. this.disposed = false;
  25. this.nextIdx = -1;
  26. this.ranges = [];
  27. this.ignoreSelectionChange = false;
  28. this.revealFirst = Boolean(this._options.alwaysRevealFirst);
  29. // hook up to diff editor for diff, disposal, and caret move
  30. this._register(this._editor.onDidDispose(() => this.dispose()));
  31. this._register(this._editor.onDidUpdateDiff(() => this._onDiffUpdated()));
  32. if (this._options.followsCaret) {
  33. this._register(this._editor.getModifiedEditor().onDidChangeCursorPosition((e) => {
  34. if (this.ignoreSelectionChange) {
  35. return;
  36. }
  37. this.nextIdx = -1;
  38. }));
  39. }
  40. if (this._options.alwaysRevealFirst) {
  41. this._register(this._editor.getModifiedEditor().onDidChangeModel((e) => {
  42. this.revealFirst = true;
  43. }));
  44. }
  45. // init things
  46. this._init();
  47. }
  48. _init() {
  49. const changes = this._editor.getLineChanges();
  50. if (!changes) {
  51. return;
  52. }
  53. }
  54. _onDiffUpdated() {
  55. this._init();
  56. this._compute(this._editor.getLineChanges());
  57. if (this.revealFirst) {
  58. // Only reveal first on first non-null changes
  59. if (this._editor.getLineChanges() !== null) {
  60. this.revealFirst = false;
  61. this.nextIdx = -1;
  62. this.next(1 /* ScrollType.Immediate */);
  63. }
  64. }
  65. }
  66. _compute(lineChanges) {
  67. // new ranges
  68. this.ranges = [];
  69. if (lineChanges) {
  70. // create ranges from changes
  71. lineChanges.forEach((lineChange) => {
  72. if (!this._options.ignoreCharChanges && lineChange.charChanges) {
  73. lineChange.charChanges.forEach((charChange) => {
  74. this.ranges.push({
  75. rhs: true,
  76. range: new Range(charChange.modifiedStartLineNumber, charChange.modifiedStartColumn, charChange.modifiedEndLineNumber, charChange.modifiedEndColumn)
  77. });
  78. });
  79. }
  80. else {
  81. if (lineChange.modifiedEndLineNumber === 0) {
  82. // a deletion
  83. this.ranges.push({
  84. rhs: true,
  85. range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedStartLineNumber + 1, 1)
  86. });
  87. }
  88. else {
  89. // an insertion or modification
  90. this.ranges.push({
  91. rhs: true,
  92. range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber + 1, 1)
  93. });
  94. }
  95. }
  96. });
  97. }
  98. // sort
  99. this.ranges.sort((left, right) => Range.compareRangesUsingStarts(left.range, right.range));
  100. this._onDidUpdate.fire(this);
  101. }
  102. _initIdx(fwd) {
  103. let found = false;
  104. const position = this._editor.getPosition();
  105. if (!position) {
  106. this.nextIdx = 0;
  107. return;
  108. }
  109. for (let i = 0, len = this.ranges.length; i < len && !found; i++) {
  110. const range = this.ranges[i].range;
  111. if (position.isBeforeOrEqual(range.getStartPosition())) {
  112. this.nextIdx = i + (fwd ? 0 : -1);
  113. found = true;
  114. }
  115. }
  116. if (!found) {
  117. // after the last change
  118. this.nextIdx = fwd ? 0 : this.ranges.length - 1;
  119. }
  120. if (this.nextIdx < 0) {
  121. this.nextIdx = this.ranges.length - 1;
  122. }
  123. }
  124. _move(fwd, scrollType) {
  125. assert.ok(!this.disposed, 'Illegal State - diff navigator has been disposed');
  126. if (!this.canNavigate()) {
  127. return;
  128. }
  129. if (this.nextIdx === -1) {
  130. this._initIdx(fwd);
  131. }
  132. else if (fwd) {
  133. this.nextIdx += 1;
  134. if (this.nextIdx >= this.ranges.length) {
  135. this.nextIdx = 0;
  136. }
  137. }
  138. else {
  139. this.nextIdx -= 1;
  140. if (this.nextIdx < 0) {
  141. this.nextIdx = this.ranges.length - 1;
  142. }
  143. }
  144. const info = this.ranges[this.nextIdx];
  145. this.ignoreSelectionChange = true;
  146. try {
  147. const pos = info.range.getStartPosition();
  148. this._editor.setPosition(pos);
  149. this._editor.revealRangeInCenter(info.range, scrollType);
  150. }
  151. finally {
  152. this.ignoreSelectionChange = false;
  153. }
  154. }
  155. canNavigate() {
  156. return this.ranges && this.ranges.length > 0;
  157. }
  158. next(scrollType = 0 /* ScrollType.Smooth */) {
  159. this._move(true, scrollType);
  160. }
  161. previous(scrollType = 0 /* ScrollType.Smooth */) {
  162. this._move(false, scrollType);
  163. }
  164. dispose() {
  165. super.dispose();
  166. this.ranges = [];
  167. this.disposed = true;
  168. }
  169. }