提交 a8b29458 编写于 作者: J Joao Moreno

🐛 remove the notion of active scm provider

introduces more changes to the SCM api

fixes #23623
fixes #23676
上级 ce46000f
......@@ -5,17 +5,16 @@
'use strict';
import { ExtensionContext, workspace, window, Disposable, commands, Uri, scm } from 'vscode';
import { ExtensionContext, workspace, window, Disposable, commands, Uri } from 'vscode';
import { findGit, Git } from './git';
import { Model } from './model';
import { GitSCMProvider } from './scmProvider';
import { CommandCenter } from './commands';
import { CheckoutStatusBar, SyncStatusBar } from './statusbar';
import { StatusBarCommands } from './statusbar';
import { GitContentProvider } from './contentProvider';
import { AutoFetcher } from './autofetch';
import { MergeDecorator } from './merge';
import { Askpass } from './askpass';
import { filterEvent } from './util';
import TelemetryReporter from 'vscode-extension-telemetry';
import * as nls from 'vscode-nls';
......@@ -46,15 +45,15 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi
}
const model = new Model(git, workspaceRootPath);
const commitTemplate = await model.getCommitTemplate();
outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path));
git.onOutput(str => outputChannel.append(str), null, disposables);
const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter);
const provider = new GitSCMProvider(model, commandCenter);
const statusBarCommands = new StatusBarCommands(model);
const provider = new GitSCMProvider(model, commandCenter, statusBarCommands, commitTemplate);
const contentProvider = new GitContentProvider(model);
const checkoutStatusBar = new CheckoutStatusBar(model);
const syncStatusBar = new SyncStatusBar(model);
const autoFetcher = new AutoFetcher(model);
const mergeDecorator = new MergeDecorator(model);
......@@ -62,8 +61,6 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi
commandCenter,
provider,
contentProvider,
checkoutStatusBar,
syncStatusBar,
autoFetcher,
mergeDecorator,
model
......@@ -77,13 +74,6 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi
commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
}
}
filterEvent(scm.onDidAcceptInputValue, () => scm.activeSourceControl === provider.sourceControl)
(commandCenter.commitWithInput, commandCenter, disposables);
if (scm.activeSourceControl === provider.sourceControl) {
scm.inputBox.value = await model.getCommitTemplate();
}
}
export function activate(context: ExtensionContext): any {
......
......@@ -7,8 +7,12 @@
import { scm, Uri, Disposable, SourceControl, SourceControlResourceGroup, Event, workspace, commands } from 'vscode';
import { Model, State } from './model';
import { StatusBarCommands } from './statusBar';
import { CommandCenter } from './commands';
import { mapEvent } from './util';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class GitSCMProvider {
......@@ -53,11 +57,22 @@ export class GitSCMProvider {
private indexGroup: SourceControlResourceGroup;
private workingTreeGroup: SourceControlResourceGroup;
constructor(private model: Model, private commandCenter: CommandCenter) {
constructor(
private model: Model,
private commandCenter: CommandCenter,
private statusBarCommands: StatusBarCommands,
private commitTemplate: string
) {
this._sourceControl = scm.createSourceControl('git', 'Git');
this._sourceControl.quickDiffProvider = this;
this.disposables.push(this._sourceControl);
this._sourceControl.commitTemplate = commitTemplate;
this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") };
this._sourceControl.quickDiffProvider = this;
this.statusBarCommands.onDidChange(this.onDidStatusBarCommandsChange, this, this.disposables);
this.onDidStatusBarCommandsChange();
this.mergeGroup = this._sourceControl.createResourceGroup(model.mergeGroup.id, model.mergeGroup.label);
this.indexGroup = this._sourceControl.createResourceGroup(model.indexGroup.id, model.indexGroup.label);
this.workingTreeGroup = this._sourceControl.createResourceGroup(model.workingTreeGroup.id, model.workingTreeGroup.label);
......@@ -90,6 +105,10 @@ export class GitSCMProvider {
commands.executeCommand('setContext', 'gitState', this.stateContextKey);
}
private onDidStatusBarCommandsChange(): void {
this._sourceControl.statusBarCommands = this.statusBarCommands.commands;
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
this.disposables = [];
......
......@@ -5,48 +5,45 @@
'use strict';
import { window, Disposable, StatusBarItem, StatusBarAlignment } from 'vscode';
import { Disposable, Command, EventEmitter, Event } from 'vscode';
import { RefType, Branch } from './git';
import { Model, Operation } from './model';
import { anyEvent, dispose } from './util';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class CheckoutStatusBar {
class CheckoutStatusBar {
private raw: StatusBarItem;
private _onDidChange = new EventEmitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
private disposables: Disposable[] = [];
constructor(private model: Model) {
this.raw = window.createStatusBarItem(StatusBarAlignment.Left, Number.MAX_VALUE - 1);
this.raw.show();
this.disposables.push(this.raw);
model.onDidChange(this.update, this, this.disposables);
this.update();
model.onDidChange(this._onDidChange.fire, this._onDidChange, this.disposables);
}
private update(): void {
get command(): Command | undefined {
const HEAD = this.model.HEAD;
if (!HEAD) {
this.raw.hide();
return;
return undefined;
}
const tag = this.model.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0];
const tagName = tag && tag.name;
const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8);
this.raw.command = 'git.checkout';
this.raw.color = 'rgb(255, 255, 255)';
this.raw.tooltip = localize('checkout', 'Checkout...');
this.raw.text = '$(git-branch) ' +
head +
(this.model.workingTreeGroup.resources.length > 0 ? '*' : '') +
(this.model.indexGroup.resources.length > 0 ? '+' : '') +
(this.model.mergeGroup.resources.length > 0 ? '!' : '');
this.raw.show();
const title = '$(git-branch) '
+ head
+ (this.model.workingTreeGroup.resources.length > 0 ? '*' : '')
+ (this.model.indexGroup.resources.length > 0 ? '+' : '')
+ (this.model.mergeGroup.resources.length > 0 ? '!' : '');
return {
command: 'git.checkout',
tooltip: localize('checkout', 'Checkout...'),
title
};
}
dispose(): void {
......@@ -60,7 +57,7 @@ interface SyncStatusBarState {
HEAD: Branch | undefined;
}
export class SyncStatusBar {
class SyncStatusBar {
private static StartState: SyncStatusBarState = {
isSyncRunning: false,
......@@ -68,22 +65,21 @@ export class SyncStatusBar {
HEAD: undefined
};
private raw: StatusBarItem;
private _onDidChange = new EventEmitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
private disposables: Disposable[] = [];
private _state: SyncStatusBarState = SyncStatusBar.StartState;
private get state() { return this._state; }
private set state(state: SyncStatusBarState) {
this._state = state;
this.render();
this._onDidChange.fire();
}
constructor(private model: Model) {
this.raw = window.createStatusBarItem(StatusBarAlignment.Left, Number.MAX_VALUE);
this.disposables.push(this.raw);
model.onDidChange(this.onModelChange, this, this.disposables);
model.onDidChangeOperations(this.onOperationsChange, this, this.disposables);
this.render();
this._onDidChange.fire();
}
private onOperationsChange(): void {
......@@ -101,10 +97,9 @@ export class SyncStatusBar {
};
}
private render(): void {
get command(): Command | undefined {
if (!this.state.hasRemotes) {
this.raw.hide();
return;
return undefined;
}
const HEAD = this.state.HEAD;
......@@ -136,20 +131,57 @@ export class SyncStatusBar {
tooltip = localize('syncing changes', "Synchronizing changes...");
}
this.raw.text = [icon, text].join(' ').trim();
this.raw.command = command;
this.raw.tooltip = tooltip;
return {
command,
title: [icon, text].join(' ').trim(),
tooltip
};
}
if (command) {
this.raw.color = '';
} else {
this.raw.color = 'rgba(255,255,255,0.7)';
dispose(): void {
this.disposables.forEach(d => d.dispose());
}
}
export class StatusBarCommands {
private syncStatusBar: SyncStatusBar;
private checkoutStatusBar: CheckoutStatusBar;
private disposables: Disposable[] = [];
constructor(model: Model) {
this.syncStatusBar = new SyncStatusBar(model);
this.checkoutStatusBar = new CheckoutStatusBar(model);
}
get onDidChange(): Event<void> {
return anyEvent(
this.syncStatusBar.onDidChange,
this.checkoutStatusBar.onDidChange
);
}
get commands(): Command[] {
const result: Command[] = [];
const checkout = this.checkoutStatusBar.command;
if (checkout) {
result.push(checkout);
}
const sync = this.syncStatusBar.command;
if (sync) {
result.push(sync);
}
this.raw.show();
return result;
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
this.syncStatusBar.dispose();
this.checkoutStatusBar.dispose();
this.disposables = dispose(this.disposables);
}
}
\ No newline at end of file
......@@ -654,6 +654,7 @@ export interface RenameProvider {
export interface Command {
id: string;
title: string;
tooltip?: string;
arguments?: any[];
}
export interface ICodeLensSymbol {
......
......@@ -4866,6 +4866,7 @@ declare module monaco.languages {
export interface Command {
id: string;
title: string;
tooltip?: string;
arguments?: any[];
}
......
......@@ -28,6 +28,11 @@ declare module 'vscode' {
*/
command: string;
/**
* A tooltip for for command, when represented in the UI.
*/
tooltip?: string;
/**
* Arguments that the command handler should be
* invoked with.
......@@ -4681,38 +4686,45 @@ declare module 'vscode' {
quickDiffProvider?: QuickDiffProvider;
/**
* Create a new [resource group](#SourceControlResourceGroup).
* Optional commit template string.
*
* The Source Control viewlet will populate the Source Control
* input with this value when appropriate.
*/
createResourceGroup(id: string, label: string): SourceControlResourceGroup;
commitTemplate?: string;
/**
* Dispose this source control.
* Optional accept input command.
*
* This command will be invoked when the user accepts the value
* in the Source Control input.
*/
dispose(): void;
}
export namespace scm {
acceptInputCommand?: Command;
/**
* The currently active [source control](#SourceControl).
* Optional status bar commands.
*
* These commands will be displayed in the editor's status bar.
*/
export let activeSourceControl: SourceControl | undefined;
statusBarCommands?: Command[];
/**
* An [event](#Event) which fires when the active [source control](#SourceControl)
* has changed.
* Create a new [resource group](#SourceControlResourceGroup).
*/
export const onDidChangeActiveSourceControl: Event<SourceControl>;
createResourceGroup(id: string, label: string): SourceControlResourceGroup;
/**
* The [input box](#SourceControlInputBox) in the Source Control viewlet.
* Dispose this source control.
*/
export const inputBox: SourceControlInputBox;
dispose(): void;
}
export namespace scm {
/**
* An [event](#Event) which fires when the user has accepted the changes.
* The [input box](#SourceControlInputBox) in the Source Control viewlet.
*/
export const onDidAcceptInputValue: Event<SourceControlInputBox>;
export const inputBox: SourceControlInputBox;
/**
* Creates a new [source control](#SourceControl) instance.
......
......@@ -253,6 +253,9 @@ export abstract class MainProcessExtensionServiceShape {
export interface SCMProviderFeatures {
hasQuickDiffProvider?: boolean;
count?: number;
commitTemplate?: string;
acceptInputCommand?: modes.Command;
statusBarCommands?: modes.Command[];
}
export interface SCMGroupFeatures {
......
......@@ -192,6 +192,10 @@ export class CommandsConverter {
result.arguments = [id];
}
if (command.tooltip) {
result.tooltip = command.tooltip;
}
return result;
}
......
......@@ -181,6 +181,43 @@ class ExtHostSourceControl implements vscode.SourceControl {
this._proxy.$updateSourceControl(this._handle, { hasQuickDiffProvider: !!quickDiffProvider });
}
private _commitTemplate: string | undefined = undefined;
get commitTemplate(): string | undefined {
return this._commitTemplate;
}
set commitTemplate(commitTemplate: string | undefined) {
this._commitTemplate = commitTemplate;
this._proxy.$updateSourceControl(this._handle, { commitTemplate });
}
private _acceptInputCommand: vscode.Command | undefined = undefined;
get acceptInputCommand(): vscode.Command | undefined {
return this._acceptInputCommand;
}
set acceptInputCommand(acceptInputCommand: vscode.Command | undefined) {
this._acceptInputCommand = acceptInputCommand;
const internal = this._commands.toInternal(acceptInputCommand);
this._proxy.$updateSourceControl(this._handle, { acceptInputCommand: internal });
}
private _statusBarCommands: vscode.Command[] | undefined = undefined;
get statusBarCommands(): vscode.Command[] | undefined {
return this._statusBarCommands;
}
set statusBarCommands(statusBarCommands: vscode.Command[] | undefined) {
this._statusBarCommands = statusBarCommands;
const internal = (statusBarCommands || []).map(c => this._commands.toInternal(c));
this._proxy.$updateSourceControl(this._handle, { statusBarCommands: internal });
}
private _handle: number = ExtHostSourceControl._handlePool++;
constructor(
......
......@@ -78,6 +78,13 @@ class MainThreadSCMProvider implements ISCMProvider {
get label(): string { return this._label; }
get contextKey(): string { return this._id; }
get commitTemplate(): string | undefined { return this.features.commitTemplate; }
get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; }
get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; }
private _onDidChangeCommitTemplate = new Emitter<string>();
get onDidChangeCommitTemplate(): Event<string> { return this._onDidChangeCommitTemplate.event; }
private _count: number | undefined = undefined;
get count(): number | undefined { return this._count; }
......@@ -93,6 +100,10 @@ class MainThreadSCMProvider implements ISCMProvider {
$updateSourceControl(features: SCMProviderFeatures): void {
this.features = assign(this.features, features);
this._onDidChange.fire();
if (typeof features.commitTemplate !== 'undefined') {
this._onDidChangeCommitTemplate.fire(this.commitTemplate);
}
}
$registerGroup(handle: number, id: string, label: string): void {
......@@ -194,7 +205,6 @@ export class MainThreadSCM extends MainThreadSCMShape {
this.scmService.onDidChangeProvider(this.onDidChangeProvider, this, this._disposables);
this.scmService.input.onDidChange(this._proxy.$onInputBoxValueChange, this._proxy, this._disposables);
this.scmService.input.onDidAccept(this._proxy.$onInputBoxAcceptChanges, this._proxy, this._disposables);
}
$registerSourceControl(handle: number, id: string, label: string): void {
......
......@@ -12,7 +12,7 @@ import { chain } from 'vs/base/common/event';
import { onUnexpectedError } from 'vs/base/common/errors';
import * as platform from 'vs/base/common/platform';
import { domEvent } from 'vs/base/browser/event';
import { IDisposable, dispose, empty as EmptyDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { Viewlet } from 'vs/workbench/browser/viewlet';
import { append, $, toggleClass } from 'vs/base/browser/dom';
......@@ -212,6 +212,7 @@ function resourceSorter(a: ISCMResource, b: ISCMResource): number {
export class SCMViewlet extends Viewlet {
private activeProvider: ISCMProvider | undefined;
private cachedDimension: Dimension;
private inputBoxContainer: HTMLElement;
private inputBox: InputBox;
......@@ -245,13 +246,21 @@ export class SCMViewlet extends Viewlet {
private setActiveProvider(activeProvider: ISCMProvider | undefined): void {
this.providerChangeDisposable.dispose();
this.activeProvider = activeProvider;
if (activeProvider) {
this.providerChangeDisposable = activeProvider.onDidChange(this.update, this);
const disposables = [activeProvider.onDidChange(this.update, this)];
if (activeProvider.onDidChangeCommitTemplate) {
disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this));
}
this.providerChangeDisposable = combinedDisposable(disposables);
} else {
this.providerChangeDisposable = EmptyDisposable;
}
this.updateInputBox();
this.updateTitleArea();
this.update();
}
......@@ -278,7 +287,7 @@ export class SCMViewlet extends Viewlet {
chain(domEvent(this.inputBox.inputElement, 'keydown'))
.map(e => new StandardKeyboardEvent(e))
.filter(e => e.equals(KeyMod.CtrlCmd | KeyCode.Enter) || e.equals(KeyMod.CtrlCmd | KeyCode.KEY_S))
.on(this.scmService.input.acceptChanges, this.scmService.input, this.disposables);
.on(this.onDidAcceptInput, this, this.disposables);
this.listContainer = append(root, $('.scm-status.show-file-icons'));
const delegate = new Delegate();
......@@ -312,6 +321,22 @@ export class SCMViewlet extends Viewlet {
return TPromise.as(null);
}
private onDidAcceptInput(): void {
if (!this.activeProvider) {
return;
}
if (!this.activeProvider.acceptInputCommand) {
return;
}
const id = this.activeProvider.acceptInputCommand.id;
const args = this.activeProvider.acceptInputCommand.arguments;
this.commandService.executeCommand(id, ...args)
.done(undefined, onUnexpectedError);
}
private update(): void {
const provider = this.scmService.activeProvider;
......@@ -326,6 +351,18 @@ export class SCMViewlet extends Viewlet {
this.list.splice(0, this.list.length, elements);
}
private updateInputBox(): void {
if (!this.activeProvider) {
return;
}
if (typeof this.activeProvider.commitTemplate === 'undefined') {
return;
}
this.inputBox.value = this.activeProvider.commitTemplate;
}
layout(dimension: Dimension = this.cachedDimension): void {
if (!dimension) {
return;
......
......@@ -25,7 +25,6 @@ export interface ISCMResourceDecorations {
}
export interface ISCMResource {
// readonly uri: URI;
readonly resourceGroup: ISCMResourceGroup;
readonly sourceUri: URI;
readonly command?: Command;
......@@ -33,7 +32,6 @@ export interface ISCMResource {
}
export interface ISCMResourceGroup {
// readonly uri: URI;
readonly provider: ISCMProvider;
readonly label: string;
readonly contextKey?: string;
......@@ -44,9 +42,12 @@ export interface ISCMProvider extends IDisposable {
readonly label: string;
readonly contextKey?: string;
readonly resources: ISCMResourceGroup[];
// TODO: Event<void>
readonly onDidChange: Event<void>;
readonly count?: number;
readonly commitTemplate?: string;
readonly onDidChangeCommitTemplate?: Event<string>;
readonly acceptInputCommand?: Command;
readonly statusBarCommands?: Command[];
getOriginalResource(uri: URI): TPromise<URI>;
}
......@@ -54,8 +55,6 @@ export interface ISCMProvider extends IDisposable {
export interface ISCMInput {
value: string;
readonly onDidChange: Event<string>;
readonly onDidAccept: Event<string>;
acceptChanges(): void;
}
export interface ISCMService {
......
......@@ -5,10 +5,11 @@
'use strict';
import { IDisposable, toDisposable, empty as EmptyDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { memoize } from 'vs/base/common/decorators';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { ISCMService, ISCMProvider, ISCMInput } from './scm';
class SCMInput implements ISCMInput {
......@@ -26,20 +27,14 @@ class SCMInput implements ISCMInput {
private _onDidChange = new Emitter<string>();
get onDidChange(): Event<string> { return this._onDidChange.event; }
private _onDidAccept = new Emitter<string>();
get onDidAccept(): Event<string> { return this._onDidAccept.event; }
acceptChanges(): void {
this._onDidAccept.fire(this._value);
}
}
export class SCMService implements ISCMService {
_serviceBrand;
private providerChangeDisposable: IDisposable = EmptyDisposable;
private activeProviderDisposable: IDisposable = EmptyDisposable;
private statusBarDisposable: IDisposable = EmptyDisposable;
private activeProviderContextKey: IContextKey<string | undefined>;
private _activeProvider: ISCMProvider | undefined;
......@@ -49,6 +44,8 @@ export class SCMService implements ISCMService {
}
set activeProvider(provider: ISCMProvider | undefined) {
this.activeProviderDisposable.dispose();
if (!provider) {
throw new Error('invalid provider');
}
......@@ -58,10 +55,11 @@ export class SCMService implements ISCMService {
}
this._activeProvider = provider;
this.activeProviderContextKey.set(provider ? provider.contextKey : void 0);
this.providerChangeDisposable.dispose();
this.activeProviderDisposable = provider.onDidChange(() => this.onDidProviderChange(provider));
this.onDidProviderChange(provider);
this.activeProviderContextKey.set(provider ? provider.contextKey : void 0);
this._onDidChangeProvider.fire(provider);
}
......@@ -75,7 +73,8 @@ export class SCMService implements ISCMService {
get input(): ISCMInput { return new SCMInput(); }
constructor(
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IStatusbarService private statusbarService: IStatusbarService
) {
this.activeProviderContextKey = contextKeyService.createKey<string | undefined>('scmProvider', void 0);
}
......@@ -101,4 +100,17 @@ export class SCMService implements ISCMService {
}
});
}
private onDidProviderChange(provider: ISCMProvider): void {
this.statusBarDisposable.dispose();
const commands = provider.statusBarCommands || [];
const disposables = commands.map(c => this.statusbarService.addEntry({
text: c.title,
tooltip: c.tooltip,
command: c.id
}, MainThreadStatusBarAlignment.LEFT, 10000));
this.statusBarDisposable = combinedDisposable(disposables);
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册