inlineDiffMargin.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  6. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  7. return new (P || (P = Promise))(function (resolve, reject) {
  8. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  9. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  10. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  11. step((generator = generator.apply(thisArg, _arguments || [])).next());
  12. });
  13. };
  14. import * as nls from '../../../nls.js';
  15. import * as dom from '../../../base/browser/dom.js';
  16. import { Action } from '../../../base/common/actions.js';
  17. import { Disposable } from '../../../base/common/lifecycle.js';
  18. import { Range } from '../../common/core/range.js';
  19. import { Codicon } from '../../../base/common/codicons.js';
  20. export class InlineDiffMargin extends Disposable {
  21. constructor(_viewZoneId, _marginDomNode, editor, diff, _contextMenuService, _clipboardService) {
  22. super();
  23. this._viewZoneId = _viewZoneId;
  24. this._marginDomNode = _marginDomNode;
  25. this.editor = editor;
  26. this.diff = diff;
  27. this._contextMenuService = _contextMenuService;
  28. this._clipboardService = _clipboardService;
  29. this._visibility = false;
  30. // make sure the diff margin shows above overlay.
  31. this._marginDomNode.style.zIndex = '10';
  32. this._diffActions = document.createElement('div');
  33. this._diffActions.className = Codicon.lightBulb.classNames + ' lightbulb-glyph';
  34. this._diffActions.style.position = 'absolute';
  35. const lineHeight = editor.getOption(58 /* lineHeight */);
  36. const lineFeed = editor.getModel().getEOL();
  37. this._diffActions.style.right = '0px';
  38. this._diffActions.style.visibility = 'hidden';
  39. this._diffActions.style.height = `${lineHeight}px`;
  40. this._diffActions.style.lineHeight = `${lineHeight}px`;
  41. this._marginDomNode.appendChild(this._diffActions);
  42. const actions = [];
  43. const isDeletion = diff.modifiedEndLineNumber === 0;
  44. // default action
  45. actions.push(new Action('diff.clipboard.copyDeletedContent', isDeletion
  46. ? (diff.originalEndLineNumber > diff.modifiedStartLineNumber
  47. ? nls.localize('diff.clipboard.copyDeletedLinesContent.label', "Copy deleted lines")
  48. : nls.localize('diff.clipboard.copyDeletedLinesContent.single.label', "Copy deleted line"))
  49. : (diff.originalEndLineNumber > diff.modifiedStartLineNumber
  50. ? nls.localize('diff.clipboard.copyChangedLinesContent.label', "Copy changed lines")
  51. : nls.localize('diff.clipboard.copyChangedLinesContent.single.label', "Copy changed line")), undefined, true, () => __awaiter(this, void 0, void 0, function* () {
  52. const range = new Range(diff.originalStartLineNumber, 1, diff.originalEndLineNumber + 1, 1);
  53. const deletedText = diff.originalModel.getValueInRange(range);
  54. yield this._clipboardService.writeText(deletedText);
  55. })));
  56. let currentLineNumberOffset = 0;
  57. let copyLineAction = undefined;
  58. if (diff.originalEndLineNumber > diff.modifiedStartLineNumber) {
  59. copyLineAction = new Action('diff.clipboard.copyDeletedLineContent', isDeletion
  60. ? nls.localize('diff.clipboard.copyDeletedLineContent.label', "Copy deleted line ({0})", diff.originalStartLineNumber)
  61. : nls.localize('diff.clipboard.copyChangedLineContent.label', "Copy changed line ({0})", diff.originalStartLineNumber), undefined, true, () => __awaiter(this, void 0, void 0, function* () {
  62. const lineContent = diff.originalModel.getLineContent(diff.originalStartLineNumber + currentLineNumberOffset);
  63. if (lineContent === '') {
  64. // empty line
  65. const eof = diff.originalModel.getEndOfLineSequence();
  66. yield this._clipboardService.writeText(eof === 0 /* LF */ ? '\n' : '\r\n');
  67. }
  68. else {
  69. yield this._clipboardService.writeText(lineContent);
  70. }
  71. }));
  72. actions.push(copyLineAction);
  73. }
  74. const readOnly = editor.getOption(80 /* readOnly */);
  75. if (!readOnly) {
  76. actions.push(new Action('diff.inline.revertChange', nls.localize('diff.inline.revertChange.label', "Revert this change"), undefined, true, () => __awaiter(this, void 0, void 0, function* () {
  77. const range = new Range(diff.originalStartLineNumber, 1, diff.originalEndLineNumber, diff.originalModel.getLineMaxColumn(diff.originalEndLineNumber));
  78. const deletedText = diff.originalModel.getValueInRange(range);
  79. if (diff.modifiedEndLineNumber === 0) {
  80. // deletion only
  81. const column = editor.getModel().getLineMaxColumn(diff.modifiedStartLineNumber);
  82. editor.executeEdits('diffEditor', [
  83. {
  84. range: new Range(diff.modifiedStartLineNumber, column, diff.modifiedStartLineNumber, column),
  85. text: lineFeed + deletedText
  86. }
  87. ]);
  88. }
  89. else {
  90. const column = editor.getModel().getLineMaxColumn(diff.modifiedEndLineNumber);
  91. editor.executeEdits('diffEditor', [
  92. {
  93. range: new Range(diff.modifiedStartLineNumber, 1, diff.modifiedEndLineNumber, column),
  94. text: deletedText
  95. }
  96. ]);
  97. }
  98. })));
  99. }
  100. const showContextMenu = (x, y) => {
  101. this._contextMenuService.showContextMenu({
  102. getAnchor: () => {
  103. return {
  104. x,
  105. y
  106. };
  107. },
  108. getActions: () => {
  109. if (copyLineAction) {
  110. copyLineAction.label =
  111. isDeletion
  112. ? nls.localize('diff.clipboard.copyDeletedLineContent.label', "Copy deleted line ({0})", diff.originalStartLineNumber + currentLineNumberOffset)
  113. : nls.localize('diff.clipboard.copyChangedLineContent.label', "Copy changed line ({0})", diff.originalStartLineNumber + currentLineNumberOffset);
  114. }
  115. return actions;
  116. },
  117. autoSelectFirstItem: true
  118. });
  119. };
  120. this._register(dom.addStandardDisposableListener(this._diffActions, 'mousedown', e => {
  121. const { top, height } = dom.getDomNodePagePosition(this._diffActions);
  122. let pad = Math.floor(lineHeight / 3);
  123. e.preventDefault();
  124. showContextMenu(e.posx, top + height + pad);
  125. }));
  126. this._register(editor.onMouseMove((e) => {
  127. if (e.target.type === 8 /* CONTENT_VIEW_ZONE */ || e.target.type === 5 /* GUTTER_VIEW_ZONE */) {
  128. const viewZoneId = e.target.detail.viewZoneId;
  129. if (viewZoneId === this._viewZoneId) {
  130. this.visibility = true;
  131. currentLineNumberOffset = this._updateLightBulbPosition(this._marginDomNode, e.event.browserEvent.y, lineHeight);
  132. }
  133. else {
  134. this.visibility = false;
  135. }
  136. }
  137. else {
  138. this.visibility = false;
  139. }
  140. }));
  141. this._register(editor.onMouseDown((e) => {
  142. if (!e.event.rightButton) {
  143. return;
  144. }
  145. if (e.target.type === 8 /* CONTENT_VIEW_ZONE */ || e.target.type === 5 /* GUTTER_VIEW_ZONE */) {
  146. const viewZoneId = e.target.detail.viewZoneId;
  147. if (viewZoneId === this._viewZoneId) {
  148. e.event.preventDefault();
  149. currentLineNumberOffset = this._updateLightBulbPosition(this._marginDomNode, e.event.browserEvent.y, lineHeight);
  150. showContextMenu(e.event.posx, e.event.posy + lineHeight);
  151. }
  152. }
  153. }));
  154. }
  155. get visibility() {
  156. return this._visibility;
  157. }
  158. set visibility(_visibility) {
  159. if (this._visibility !== _visibility) {
  160. this._visibility = _visibility;
  161. if (_visibility) {
  162. this._diffActions.style.visibility = 'visible';
  163. }
  164. else {
  165. this._diffActions.style.visibility = 'hidden';
  166. }
  167. }
  168. }
  169. _updateLightBulbPosition(marginDomNode, y, lineHeight) {
  170. const { top } = dom.getDomNodePagePosition(marginDomNode);
  171. const offset = y - top;
  172. const lineNumberOffset = Math.floor(offset / lineHeight);
  173. const newTop = lineNumberOffset * lineHeight;
  174. this._diffActions.style.top = `${newTop}px`;
  175. if (this.diff.viewLineCounts) {
  176. let acc = 0;
  177. for (let i = 0; i < this.diff.viewLineCounts.length; i++) {
  178. acc += this.diff.viewLineCounts[i];
  179. if (lineNumberOffset < acc) {
  180. return i;
  181. }
  182. }
  183. }
  184. return lineNumberOffset;
  185. }
  186. }