diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3cdf946107867321e98c4e583654f7ad102530fe..6997c3a80e25b788e6cfe2daf7d0203796add634 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, computeDiff, Range, WorkspaceEdit, Position } from 'vscode'; +import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange } from 'vscode'; import { Ref, RefType, Git } from './git'; import { Model, Resource, Status, CommitOptions } from './model'; import * as staging from './staging'; @@ -60,15 +60,23 @@ class CheckoutRemoteHeadItem extends CheckoutItem { } } -const Commands: { commandId: string; key: string; method: Function; skipModelCheck: boolean; }[] = []; +interface Command { + commandId: string; + key: string; + method: Function; + skipModelCheck: boolean; + requiresDiffInformation: boolean; +} + +const Commands: Command[] = []; -function command(commandId: string, skipModelCheck = false): Function { +function command(commandId: string, skipModelCheck = false, requiresDiffInformation = false): Function { return (target: any, key: string, descriptor: any) => { if (!(typeof descriptor.value === 'function')) { throw new Error('not supported'); } - Commands.push({ commandId, key, method: descriptor.value, skipModelCheck }); + Commands.push({ commandId, key, method: descriptor.value, skipModelCheck, requiresDiffInformation }); }; } @@ -88,7 +96,15 @@ export class CommandCenter { } this.disposables = Commands - .map(({ commandId, key, method, skipModelCheck }) => commands.registerCommand(commandId, this.createCommand(commandId, key, method, skipModelCheck))); + .map(({ commandId, key, method, skipModelCheck, requiresDiffInformation }) => { + const command = this.createCommand(commandId, key, method, skipModelCheck); + + if (requiresDiffInformation) { + return commands.registerDiffInformationCommand(commandId, command); + } else { + return commands.registerCommand(commandId, command); + } + }); } @command('git.refresh') @@ -288,8 +304,8 @@ export class CommandCenter { return await this.model.add(); } - @command('git.stageSelectedRanges') - async stageSelectedRanges(): Promise { + @command('git.stageSelectedRanges', false, true) + async stageSelectedRanges(diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -305,7 +321,6 @@ export class CommandCenter { const originalUri = modifiedUri.with({ scheme: 'git', query: '~' }); const originalDocument = await workspace.openTextDocument(originalUri); - const diffs = await computeDiff(originalDocument, modifiedDocument); const selections = textEditor.selections; const selectedDiffs = diffs.filter(diff => { const modifiedRange = diff.modifiedEndLineNumber === 0 @@ -323,8 +338,8 @@ export class CommandCenter { await this.model.stage(modifiedUri, result); } - @command('git.revertSelectedRanges') - async revertSelectedRanges(): Promise { + @command('git.revertSelectedRanges', false, true) + async revertSelectedRanges(diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -340,7 +355,6 @@ export class CommandCenter { const originalUri = modifiedUri.with({ scheme: 'git', query: '~' }); const originalDocument = await workspace.openTextDocument(originalUri); - const diffs = await computeDiff(originalDocument, modifiedDocument); const selections = textEditor.selections; const selectedDiffs = diffs.filter(diff => { const modifiedRange = diff.modifiedEndLineNumber === 0 @@ -385,8 +399,8 @@ export class CommandCenter { return await this.model.revertFiles(); } - @command('git.unstageSelectedRanges') - async unstageSelectedRanges(): Promise { + @command('git.unstageSelectedRanges', false, true) + async unstageSelectedRanges(diffs: LineChange[]): Promise { const textEditor = window.activeTextEditor; if (!textEditor) { @@ -402,7 +416,6 @@ export class CommandCenter { const originalUri = modifiedUri.with({ scheme: 'git', query: 'HEAD' }); const originalDocument = await workspace.openTextDocument(originalUri); - const diffs = await computeDiff(originalDocument, modifiedDocument); const selections = textEditor.selections; const selectedDiffs = diffs.filter(diff => { const modifiedRange = diff.modifiedEndLineNumber === 0 diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 307de4a1b5c8544b36d0122ba7bb3e7719b88ea8..7c89c247fcee6f11d941f6bc740fef15974f3b96 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -639,5 +639,22 @@ declare module 'vscode' { readonly modifiedEndLineNumber: number; } - export function computeDiff(oneDocument: TextDocument, otherDocument: TextDocument): Thenable; -} + export namespace commands { + + /** + * Registers a diff information command that can be invoked via a keyboard shortcut, + * a menu item, an action, or directly. + * + * Diff information commands are different from ordinary [commands](#commands.registerCommand) as + * they only execute when there is an active diff editor when the command is called, and the diff + * information has been computed. Also, the command handler of an editor command has access to + * the diff information. + * + * @param command A unique identifier for the command. + * @param callback A command handler function with access to the [diff information](#LineChange). + * @param thisArg The `this` context used when invoking the handler function. + * @return Disposable which unregisters this command on disposal. + */ + export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg?: any): Disposable; + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 4ce9f9f75c01644127379c5db7a91fa5a6b73c5e..9f545cbd71026e00bcea4780eefab62b06917059 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -33,7 +33,6 @@ import { ExtHostEditors } from 'vs/workbench/api/node/extHostTextEditors'; import { ExtHostLanguages } from 'vs/workbench/api/node/extHostLanguages'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; -import { computeDiff } from 'vs/workbench/api/node/extHostFunctions'; import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; import * as extHostTypes from 'vs/workbench/api/node/extHostTypes'; import URI from 'vs/base/common/uri'; @@ -167,6 +166,18 @@ export function createApiFactory(initData: IInitData, threadService: IThreadServ }); }); }, + registerDiffInformationCommand(id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable { + return extHostCommands.registerCommand(id, async (...args: any[]) => { + let activeTextEditor = extHostEditors.getActiveTextEditor(); + if (!activeTextEditor) { + console.warn('Cannot execute ' + id + ' because there is no active text editor.'); + return undefined; + } + + const diff = await extHostEditors.getDiffInformation(activeTextEditor.id); + callback.apply(thisArg, [diff, ...args]); + }); + }, executeCommand(id: string, ...args: any[]): Thenable { return extHostCommands.executeCommand(id, ...args); }, @@ -499,7 +510,6 @@ export function createApiFactory(initData: IInitData, threadService: IThreadServ ViewColumn: extHostTypes.ViewColumn, WorkspaceEdit: extHostTypes.WorkspaceEdit, // functions - computeDiff, FileLocationKind: extHostTypes.FileLocationKind, ApplyToKind: extHostTypes.ApplyToKind, RevealKind: extHostTypes.RevealKind, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 1f8924d33bf62d5ce52770cc9450ebb67b20668d..bda5b5ff0fbaed24aeed6d6a167175039292c9e8 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -140,6 +140,7 @@ export abstract class MainThreadEditorsShape { $trySetSelections(id: string, selections: editorCommon.ISelection[]): TPromise { throw ni(); } $tryApplyEdits(id: string, modelVersionId: number, edits: editorCommon.ISingleEditOperation[], opts: IApplyEditsOptions): TPromise { throw ni(); } $tryInsertSnippet(id: string, template: string, selections: editorCommon.IRange[], opts: IUndoStopOptions): TPromise { throw ni(); } + $getDiffInformation(id: string): TPromise { throw ni(); } } export abstract class MainThreadTreeExplorersShape { diff --git a/src/vs/workbench/api/node/extHostFunctions.ts b/src/vs/workbench/api/node/extHostFunctions.ts deleted file mode 100644 index cb21a24ea0eeb079e9f54c0a7fdcbd41c8366d02..0000000000000000000000000000000000000000 --- a/src/vs/workbench/api/node/extHostFunctions.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as vscode from 'vscode'; -import { toThenable } from 'vs/base/common/async'; -import { DiffComputer } from 'vs/editor/common/diff/diffComputer'; - -function getTextDocumentLines(document: vscode.TextDocument): string[] { - const result = []; - for (let i = 0; i < document.lineCount; i++) { - result.push(document.lineAt(i).text); - } - return result; -} - -export function computeDiff(oneDocument: vscode.TextDocument, otherDocument: vscode.TextDocument): Thenable { - const oneLines = getTextDocumentLines(oneDocument); - const otherLines = getTextDocumentLines(otherDocument); - const computer = new DiffComputer(oneLines, otherLines, { - shouldPostProcessCharChanges: false, - shouldIgnoreTrimWhitespace: false, // options? - shouldConsiderTrimWhitespaceInEmptyCase: false - }); - - return toThenable(computer.computeDiff()); -} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostTextEditor.ts b/src/vs/workbench/api/node/extHostTextEditor.ts index 98eedfcbce54dad20e648a758ee6ffb475895158..b0f0ec172e66e540ea01f264ad6e13ad7b92b300 100644 --- a/src/vs/workbench/api/node/extHostTextEditor.ts +++ b/src/vs/workbench/api/node/extHostTextEditor.ts @@ -322,6 +322,8 @@ export class ExtHostTextEditor implements vscode.TextEditor { private _viewColumn: vscode.ViewColumn; private _disposed: boolean = false; + get id(): string { return this._id; } + constructor(proxy: MainThreadEditorsShape, id: string, document: ExtHostDocumentData, selections: Selection[], options: IResolvedTextEditorConfiguration, viewColumn: vscode.ViewColumn) { this._proxy = proxy; this._id = id; diff --git a/src/vs/workbench/api/node/extHostTextEditors.ts b/src/vs/workbench/api/node/extHostTextEditors.ts index fb4fd75dfd35dcf58efca2f91f8471a496466fd1..db7f7cb4e9c61bc618b1aba2fe945a00a38452ae 100644 --- a/src/vs/workbench/api/node/extHostTextEditors.ts +++ b/src/vs/workbench/api/node/extHostTextEditors.ts @@ -6,12 +6,13 @@ import URI from 'vs/base/common/uri'; import Event, { Emitter } from 'vs/base/common/event'; +import { toThenable } from 'vs/base/common/async'; import { TPromise } from 'vs/base/common/winjs.base'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import { TextEditorSelectionChangeKind } from './extHostTypes'; import { IResolvedTextEditorConfiguration, ISelectionChangeEvent } from 'vs/workbench/api/node/mainThreadEditor'; import * as TypeConverters from './extHostTypeConverters'; -import { TextEditorDecorationType } from './extHostTextEditor'; +import { TextEditorDecorationType, ExtHostTextEditor } from './extHostTextEditor'; import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors'; import { MainContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextEditorPositionData } from './extHost.protocol'; import * as vscode from 'vscode'; @@ -46,7 +47,7 @@ export class ExtHostEditors extends ExtHostEditorsShape { this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e)); } - getActiveTextEditor(): vscode.TextEditor { + getActiveTextEditor(): ExtHostTextEditor { return this._extHostDocumentsAndEditors.activeEditor(); } @@ -102,4 +103,8 @@ export class ExtHostEditors extends ExtHostEditorsShape { } } } + + getDiffInformation(id: string): Thenable { + return toThenable(this._proxy.$getDiffInformation(id)); + } } diff --git a/src/vs/workbench/api/node/mainThreadEditors.ts b/src/vs/workbench/api/node/mainThreadEditors.ts index 9c3833207ddf613b66109332ba13ffec9c744e5c..23d8972ab30040697c22ca4468e0e5890600dbe1 100644 --- a/src/vs/workbench/api/node/mainThreadEditors.ts +++ b/src/vs/workbench/api/node/mainThreadEditors.ts @@ -8,7 +8,7 @@ import URI from 'vs/base/common/uri'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { ISingleEditOperation, ISelection, IRange, IDecorationRenderOptions, IDecorationOptions } from 'vs/editor/common/editorCommon'; +import { ISingleEditOperation, ISelection, IRange, IDecorationRenderOptions, IDecorationOptions, ILineChange } from 'vs/editor/common/editorCommon'; import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; @@ -200,6 +200,24 @@ export class MainThreadEditors extends MainThreadEditorsShape { $removeTextEditorDecorationType(key: string): void { this._codeEditorService.removeDecorationType(key); + } + + $getDiffInformation(id: string): TPromise { + const editor = this._documentsAndEditors.getEditor(id); + + if (!editor) { + return TPromise.wrapError('No such TextEditor'); + } + + const codeEditor = editor.getCodeEditor(); + const codeEditorId = codeEditor.getId(); + const diffEditors = this._codeEditorService.listDiffEditors(); + const [diffEditor] = diffEditors.filter(d => d.getOriginalEditor().getId() === codeEditorId || d.getModifiedEditor().getId() === codeEditorId); + + if (!diffEditor) { + return TPromise.as([]); + } + return TPromise.as(diffEditor.getLineChanges()); } } diff --git a/src/vs/workbench/test/node/api/extHostTextEditor.test.ts b/src/vs/workbench/test/node/api/extHostTextEditor.test.ts index 87e55295017aca40cca0927b45d633a81399b8bc..fdb5aedf62ce36ddbb8ba54e5eaa76f8fc7b8f84 100644 --- a/src/vs/workbench/test/node/api/extHostTextEditor.test.ts +++ b/src/vs/workbench/test/node/api/extHostTextEditor.test.ts @@ -64,7 +64,8 @@ suite('ExtHostTextEditorOptions', () => { $tryRevealRange: undefined, $trySetSelections: undefined, $tryApplyEdits: undefined, - $tryInsertSnippet: undefined + $tryInsertSnippet: undefined, + $getDiffInformation: undefined }; opts = new ExtHostTextEditorOptions(mockProxy, '1', { tabSize: 4,