Change:
There's probably already a feature request for this but I can't find it. I want to be able to select a change in the diff editor and revert it, same as how I can stage it. In SourceTree this is called "discard selected lines" although "revert" seems better, unless it's "discard selected changes". I'm working on a PR for this.
I think it is connected with

Discussion:
import diffei = require('vs/workbench/common/editor/diffEditorInput'); import { IGitService, Status, IFileStatus, StatusType } from 'vs/workbench/parts/git/common/git'; import gitei = require('vs/workbench/parts/git/browser/gitEditorInputs'); <del> import { getSelectedChanges, applyChangesToModel } from 'vs/workbench/parts/git/common/stageRanges'; </del> <ins> import { getSelectedChanges, applyChangesToModel, getChangeRevertEdits } from 'vs/workbench/parts/git/common/stageRanges'; </ins> import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';

} } <ins> export class RevertRangesAction extends baseeditor.EditorInputAction { static ID = 'workbench.action.git.revertRanges'; static LABEL = nls.localize('revertSelectedLines', "Revert Selected Lines"); private editorService: IWorkbenchEditorService; private editor: editorbrowser.IDiffEditor; constructor(editor: tdeditor.TextDiffEditor, @IWorkbenchEditorService editorService: IWorkbenchEditorService) { super(RevertRangesAction.ID, RevertRangesAction.LABEL); this.editorService = editorService; this.editor = editor.getControl(); this.editor.onDidChangeCursorSelection(() => this.updateEnablement()); this.editor.onDidUpdateDiff(() => this.updateEnablement()); this.class = 'git-action revert-ranges'; } public isEnabled(): boolean { if (!super.isEnabled()) { return false; } if (!this.editorService) { return false; } const changes = this.editor.getLineChanges(); const selections = this.editor.getSelections(); if (!changes || !selections || selections.length === 0) { return false; } return getSelectedChanges(changes, selections).length > 0; } public run(): TPromise<any> { const selections = this.editor.getSelections(); const changes = getSelectedChanges(this.editor.getLineChanges(), selections); const {original, modified} = this.editor.getModel(); const revertEdits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, revertEdits, () => selections); modified.pushStackElement(); return TPromise.wrap(null); } private updateEnablement(): void { this.enabled = this.isEnabled(); } } </ins> class FileEditorActionContributor extends baseeditor.EditorInputActionContributor { private instantiationService: IInstantiationService;

return [this.instantiationService.createInstance(UnstageRangesAction, <tdeditor.TextDiffEditor>context.editor)]; } <del> return [this.instantiationService.createInstance(StageRangesAction, <tdeditor.TextDiffEditor>context.editor)]; </del> <ins> return [ this.instantiationService.createInstance(StageRangesAction, <tdeditor.TextDiffEditor>context.editor), this.instantiationService.createInstance(RevertRangesAction, <tdeditor.TextDiffEditor>context.editor)]; </ins> } }

*--------------------------------------------------------------------------------------------*/ 'use strict'; <del> import { IChange, IModel } from 'vs/editor/common/editorCommon'; </del> <ins> import { IChange, IModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/editorCommon'; </ins> import { Range } from 'vs/editor/common/core/range'; <ins> import { Position } from 'vs/editor/common/core/position'; </ins> import { Selection } from 'vs/editor/common/core/selection'; <ins> import { EditOperation } from 'vs/editor/common/core/editOperation'; /** * Represents the selected portion of an IChange, and includes the start/end line numbers of the full change */ export class SelectedChange implements IChange { readonly originalStartLineNumber: number; readonly originalEndLineNumber: number; readonly modifiedStartLineNumber: number; readonly modifiedEndLineNumber: number; readonly fullModifiedStartLineNumber: number; readonly fullModifiedEndLineNumber: number; constructor(selected: IChange, full: IChange) { this.originalStartLineNumber = selected.originalStartLineNumber; this.originalEndLineNumber = selected.originalEndLineNumber; this.modifiedStartLineNumber = selected.modifiedStartLineNumber; this.modifiedEndLineNumber = selected.modifiedEndLineNumber; this.fullModifiedStartLineNumber = full.modifiedStartLineNumber; this.fullModifiedEndLineNumber = full.modifiedEndLineNumber; } /** * True when the change is entirely selected */ get isCompletelySelected(): boolean { return this.modifiedStartLineNumber === this.fullModifiedStartLineNumber && this.modifiedEndLineNumber === this.fullModifiedEndLineNumber; } } </ins> function sortChanges(changes: IChange[]): void { changes.sort((left, right) => {

* Returns all selected changes (there can be multiple selections due to multiple cursors). * If a change is partially selected, the selected part of the change will be returned. */ <del> export function getSelectedChanges(changes: IChange[], selections: Selection[]): IChange[] { </del> <ins> export function getSelectedChanges(changes: IChange[], selections: Selection[]): SelectedChange[] { </ins> sortChanges(changes); sortSelections(selections); <del> var result: IChange[] = []; </del> <ins> var result: SelectedChange[] = []; </ins> var currentSelection = 0; var lastLineAdded = -1;

if (intersectedChange !== null) { // Each change needs to be disjoint so we check if we already added this line. if (lastLineAdded !== intersectedChange.modifiedStartLineNumber) { <del> result.push(intersectedChange); </del> <ins> result.push(new SelectedChange(intersectedChange, changes[i])); </ins> lastLineAdded = intersectedChange.modifiedEndLineNumber; } else { // Update change such that we do not add same line twice. intersectedChange.modifiedStartLineNumber++; if (intersectedChange.modifiedStartLineNumber <= intersectedChange.modifiedEndLineNumber) { <del> result.push(intersectedChange); </del> <ins> result.push(new SelectedChange(intersectedChange, changes[i])); </ins> lastLineAdded = intersectedChange.modifiedEndLineNumber; } }

} return result; <ins> } export function getChangeRevertEdits(original: IModel, modified: IModel, changes: SelectedChange[]): IIdentifiedSingleEditOperation[] { sortChanges(changes); const getDeleteOperation = (change: IChange) => { const fullRange = getLinesRangeWithOneSurroundingNewline(modified, change.modifiedStartLineNumber, change.modifiedEndLineNumber); return EditOperation.delete(fullRange); }; return changes.map((change, i) => { if (isInsertion(change)) { // Delete inserted range return getDeleteOperation(change); } else if (isDeletion(change)) { // Get the original lines and insert at the deleted position const value = original.getValueInRange(getLinesRangeWithOneSurroundingNewline(original, change.originalStartLineNumber, change.originalEndLineNumber)); return EditOperation.insert(new Position(change.modifiedStartLineNumber + 1, 1), value); } else if (change.isCompletelySelected) { // If the entire change is selected, then revert the whole thing. const value = original.getValueInRange(new Range(change.originalStartLineNumber, 1, change.originalEndLineNumber + 1, 1)); return EditOperation.replace(new Range(change.modifiedStartLineNumber, 1, change.modifiedEndLineNumber + 1, 1), value); } else { // If only a portion is selected, replace with the matching lines - e.g. if lines 2-4 are selected, replace with lines 2-4 from the original model (if they exist) const copyOffset = change.modifiedStartLineNumber - change.fullModifiedStartLineNumber; const numLinesToCopy = change.modifiedEndLineNumber - change.modifiedStartLineNumber; const copyStartLine = change.originalStartLineNumber + copyOffset; const copyEndLine = Math.min(copyStartLine + numLinesToCopy, original.getLineCount()); if (copyStartLine > copyEndLine) { return getDeleteOperation(change); } // Compute the range to copy, and intersect with the full original range to validate const originalRange = new Range(change.originalStartLineNumber, 1, change.originalEndLineNumber, original.getLineMaxColumn(change.originalEndLineNumber)); const rangeToCopy = originalRange.intersectRanges( new Range(copyStartLine, 1, copyEndLine, original.getLineMaxColumn(copyEndLine))); // No intersection, so delete the added text if (!rangeToCopy) { return getDeleteOperation(change); } const value = original.getValueInRange(rangeToCopy); return EditOperation.replace(new Range(change.modifiedStartLineNumber, 1, change.modifiedEndLineNumber, modified.getLineMaxColumn(change.modifiedEndLineNumber)), value); } }); } function getLinesRangeWithOneSurroundingNewline(model: IModel, startLine: number, endLine: number): Range { let startColumn = 1; let endColumn = model.getLineMaxColumn(endLine); if (endLine < model.getLineCount()) { endLine++; endColumn = 1; } else if (startLine > 1) { startLine--; startColumn = model.getLineMaxColumn(startLine); } return new Range(startLine, startColumn, endLine, endColumn); </ins> }  No newline at end of file

import * as assert from 'assert'; <del> import { getSelectedChanges, applyChangesToModel } from 'vs/workbench/parts/git/common/stageRanges'; </del> <ins> import { SelectedChange, getSelectedChanges, applyChangesToModel, getChangeRevertEdits } from 'vs/workbench/parts/git/common/stageRanges'; </ins> import { Model } from 'vs/editor/common/model/model'; import { IChange } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection';

}; } <ins> function createSelectedChange(modifiedStart: number, modifiedEnd: number, originalStart: number, originalEnd: number, fullModifiedStart = modifiedStart, fullModifiedEnd = modifiedEnd): SelectedChange { return new SelectedChange( createChange(modifiedStart, modifiedEnd, originalStart, originalEnd), createChange(fullModifiedStart, fullModifiedEnd, originalStart, originalEnd)); } </ins> suite('Git - Stage ranges', () => { test('Get selected changes test - no change selected (selection before changes)', () => {

expected.dispose(); }); <del> test('Apply changes to model - multiple changes 2 - insertion, deletion and modification', () => { var original = createModel(' One n Two n Three n Four n Five n Six n Seven n Eight n Nine n Ten '); var modified = createModel(' Two n Three n INSERTED n Four n Six n 7 n Eight n 9 n CHANGEIGNORED n INSERTED'); var changes: IChange[] = []; changes.push(createChange(1, 0, 1, 1), createChange(3, 3, 3, 0), createChange(5, 0, 5, 5), createChange(6, 8, 7, 9), createChange(10, 10, 10, 0)); var result = applyChangesToModel(original, modified, changes); var expected = createModel(' Two n Three n INSERTED n Four n Six n 7 n Eight n 9 n Ten n INSERTED'); assert.equal(result, expected.getValue()); </del> <ins> test('Revert changes on model - no changes', () => { var original = createModel('One line that is equal. '); var modified = createModel('One line that is equal. n Second line is new.'); var changes: SelectedChange[] = []; const edits = getChangeRevertEdits(original, modified, changes); assert.equal(edits.length, 0); original.dispose(); modified.dispose(); }); test('Revert changes on model - one line insertion at the beginning', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel(' One line that is equal. '); const modified = createModel('Inserted line is new. n One line that is equal. '); const changes: SelectedChange[] = []; changes.push(createSelectedChange(1, 1, 0, 0)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); }); test('Revert changes on model - one line insertion in the middle', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('One line that is equal. n Last line same. '); const modified = createModel('One line that is equal. n Second line is new. n Last line same. '); const changes: SelectedChange[] = []; changes.push(createSelectedChange(2, 2, 1, 0)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); }); test('Revert changes on model - one line insertion at the end', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('One line that is equal. '); const modified = createModel('One line that is equal. n Second line is new.'); const changes: SelectedChange[] = []; changes.push(createSelectedChange(2, 2, 1, 0)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); }); test('Revert changes on model - one line deletion at the beginning', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('First line is deleted. n One line that is equal. '); const modified = createModel(' One line that is equal. '); const changes: SelectedChange[] = []; changes.push(createSelectedChange(0, 0, 1, 1)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); }); test('Revert changes on model - one line deletion in the middle', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('One line that is equal. n Second line is deleted. n Last line same. '); const modified = createModel('One line that is equal. n Last line same. '); const changes: SelectedChange[] = []; changes.push(createSelectedChange(1, 0, 2, 2)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); }); test('Revert changes on model - one line deletion at the end', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('One line that is equal. n Second line is deleted.'); const modified = createModel('One line that is equal. '); const changes: SelectedChange[] = []; changes.push(createSelectedChange(2, 0, 2, 2)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); }); test('Revert changes on model - three empty lines insertion in the middle', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('hellon theren isidorn'); const modified = createModel('hellon theren n n n isidorn'); const changes: SelectedChange[] = []; changes.push(createSelectedChange(3, 5, 2, 0)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); }); test('Revert changes on model - one line deletion', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('One line that is equal. n Second line is old. n Third line same. n Forth line not important'); const modified = createModel('One line that is equal. n Third line same. '); const changes: SelectedChange[] = []; changes.push(createSelectedChange(1, 0, 2, 2)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = createModel('One line that is equal. n Second line is old. n Third line same. '); assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); expected.dispose(); }); test('Revert changes on model - one multi line change', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('One line that is equal. n Second line is different. n Third line also different. n Forth line is same. n Fifth line is different.'); const modified = createModel('One line that is equal. n 2nd line is different. n 3rd line also different. n Forth line is same. n 5th line is different.'); const changes: SelectedChange[] = []; changes.push(createSelectedChange(2, 3, 2, 3)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = createModel('One line that is equal. n Second line is different. n Third line also different. n Forth line is same. n 5th line is different.'); assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); expected.dispose(); }); test('Revert changes on model - one multi line change - partial revert', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel('One line that is equal. n Second line is different. n Third line also different. n Forth line is same. n Fifth line is different.'); const modified = createModel('One line that is equal. n 2nd line is different. n 3rd line also different. n Forth line is same. n 5th line is different.'); const changes: SelectedChange[] = []; changes.push(createSelectedChange(3, 3, 2, 3, 2, 3)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = createModel('One line that is equal. n 2nd line is different. n Third line also different. n Forth line is same. n 5th line is different.'); assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); expected.dispose(); }); test('Revert changes on model - multiple small changes', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel(' One n Two n Three n Four n Five n Six n Seven n Eight n'); const modified = createModel(' One n 2 n Three n 4 n 5 n Six n 7 n 8 n'); const changes: SelectedChange[] = []; changes.push(createSelectedChange(1, 2, 1, 2), createSelectedChange(4, 5, 4, 5), createSelectedChange(7, 8, 7, 8)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); expected.dispose(); }); test('Revert changes on model - multiple changes - insertion, deletion and modification', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel(' One n Two n Three n Four n Five n Six n Seven n Eight n Nine n Ten'); const modified = createModel(' 1 n Three n 4 n 5 n Six n 7 n NEWLINE n Eight '); const changes: SelectedChange[] = []; changes.push(createSelectedChange(1, 1, 1, 1), createSelectedChange(1, 0, 2, 2), createSelectedChange(3, 4, 4, 5), createSelectedChange(6, 7, 7, 7), createSelectedChange(8, 0, 9, 10)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); original.dispose(); modified.dispose(); expected.dispose(); }); test('Revert changes on model - multiple changes - partial revert', () => { const selections = [new Selection(1, 1, 1, 1)]; const original = createModel(' One n Two n Three n Four n Five n Six n Seven n Eight n Nine n Ten'); const modified = createModel(' 1 n Three n 4 n 5 n Six n 7 n NEWLINE n Eight '); const changes: SelectedChange[] = []; changes.push(createSelectedChange(1, 1, 1, 1), createSelectedChange(1, 0, 2, 2), createSelectedChange(3, 4, 4, 5), createSelectedChange(6, 7, 7, 7), createSelectedChange(8, 0, 9, 10)); const edits = getChangeRevertEdits(original, modified, changes); modified.pushEditOperations(selections, edits, () => selections); const expected = original; assert.equal(modified.getValue(), expected.getValue()); </ins> original.dispose(); modified.dispose(); expected.dispose();