git: commit

This commit is contained in:
Joao Moreno 2017-01-31 11:15:52 +01:00
parent b276b5e04b
commit cb36e6c217
8 changed files with 126 additions and 35 deletions

View file

@ -94,6 +94,11 @@
"dark": "resources/icons/dark/clean.svg"
}
},
{
"command": "git.commit",
"title": "%command.commit%",
"category": "Git"
},
{
"command": "git.commitStaged",
"title": "%command.commitStaged%",
@ -165,6 +170,14 @@
"category": "Git"
}
],
"keybindings": [
{
"command": "git.commitWithInput",
"key": "ctrl+enter",
"mac": "cmd+enter",
"when": "inSCMInput"
}
],
"menus": {
"scm/title": [
{

View file

@ -8,6 +8,7 @@
"command.unstageAll": "Unstage All",
"command.clean": "Clean",
"command.cleanAll": "Clean All",
"command.commit": "Commit",
"command.commitStaged": "Commit Staged",
"command.commitStagedSigned": "Commit Staged (Signed Off)",
"command.commitAll": "Commit All",

View file

@ -8,6 +8,7 @@
import { Uri, commands, scm, Disposable, SCMResourceGroup, SCMResource, window, workspace, QuickPickItem, OutputChannel } from 'vscode';
import { IRef, RefType } from './git';
import { Model, Resource, Status } from './model';
import { CommitController } from './commit';
import * as path from 'path';
import * as nls from 'vscode-nls';
@ -125,7 +126,11 @@ export class CommandCenter {
private disposables: Disposable[];
constructor(private model: Model, private outputChannel: OutputChannel) {
constructor(
private model: Model,
private commitController: CommitController,
private outputChannel: OutputChannel
) {
this.disposables = CommandCenter.Commands
.map(({ commandId, method }) => commands.registerCommand(commandId, method, this));
}
@ -286,7 +291,7 @@ export class CommandCenter {
return;
}
return await this.model.clean(resource);
await this.model.clean(resource);
}
@CommandCenter.Command('git.cleanAll')
@ -301,13 +306,45 @@ export class CommandCenter {
return;
}
return await this.model.clean(...this.model.workingTreeGroup.resources);
await this.model.clean(...this.model.workingTreeGroup.resources);
}
@CommandCenter.CatchErrors
async commit(message: string): Promise<void> {
private async _commit(fn: () => Promise<string>): Promise<boolean> {
if (this.model.indexGroup.resources.length === 0 && this.model.workingTreeGroup.resources.length === 0) {
window.showInformationMessage(localize('no changes', "There are no changes to commit."));
return false;
}
const message = await fn();
if (!message) {
// TODO@joao: show modal dialog to confirm empty message commit
return false;
}
const all = this.model.indexGroup.resources.length === 0;
return this.model.commit(message, { all });
await this.model.commit(message, { all });
return true;
}
@CommandCenter.Command('git.commit')
@CommandCenter.CatchErrors
async commit(): Promise<void> {
await this._commit(async () => await window.showInputBox({
placeHolder: localize('commit message', "Commit message"),
prompt: localize('provide commit message', "Please provide a commit message")
}));
}
@CommandCenter.Command('git.commitWithInput')
@CommandCenter.CatchErrors
async commitWithInput(): Promise<void> {
const didCommit = await this._commit(async () => this.commitController.message);
if (didCommit) {
this.commitController.message = '';
}
}
@CommandCenter.Command('git.commitStaged')

View file

@ -5,15 +5,16 @@
'use strict';
import { workspace, window, languages, Disposable, Uri, TextDocumentChangeEvent, HoverProvider, Hover, TextEditor, Position, TextDocument, Range, TextEditorDecorationType } from 'vscode';
import { workspace, window, languages, Disposable, Uri, TextDocumentChangeEvent, HoverProvider, Hover, TextEditor, Position, TextDocument, Range, TextEditorDecorationType, WorkspaceEdit } from 'vscode';
import { Model } from './model';
import { filterEvent } from './util';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const scmInputUri = Uri.parse('scm:input');
function isSCMInput(uri: Uri) {
return uri.toString() === 'scm:input';
return uri.toString() === scmInputUri.toString();
}
interface Diagnostic {
@ -22,7 +23,7 @@ interface Diagnostic {
}
// TODO@Joao: hover dissapears if editor is scrolled
export class CommitHandler implements HoverProvider {
export class CommitController implements HoverProvider {
private visibleTextEditorsDisposable: Disposable;
private editor: TextEditor;
@ -30,6 +31,28 @@ export class CommitHandler implements HoverProvider {
private decorationType: TextEditorDecorationType;
private disposables: Disposable[] = [];
get message(): string | undefined {
if (!this.editor) {
return;
}
return this.editor.document.getText();
}
set message(message: string | undefined) {
if (!this.editor || message === undefined) {
return;
}
const document = this.editor.document;
const start = document.lineAt(0).range.start;
const end = document.lineAt(document.lineCount - 1).range.end;
const range = new Range(start, end);
const edit = new WorkspaceEdit();
edit.replace(scmInputUri, range, message);
workspace.applyEdit(edit);
}
constructor(private model: Model) {
this.visibleTextEditorsDisposable = window.onDidChangeVisibleTextEditors(this.onVisibleTextEditors, this);
this.onVisibleTextEditors(window.visibleTextEditors);

View file

@ -15,7 +15,7 @@ import { filterEvent, anyEvent } from './util';
import { GitContentProvider } from './contentProvider';
import { AutoFetcher } from './autofetch';
import { MergeDecorator } from './merge';
import { CommitHandler } from './commit';
import { CommitController } from './commit';
import * as nls from 'vscode-nls';
const localize = nls.config()();
@ -42,16 +42,17 @@ async function init(disposables: Disposable[]): Promise<void> {
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(model, outputChannel);
const commitHandler = new CommitController(model);
const commandCenter = new CommandCenter(model, commitHandler, outputChannel);
const provider = new GitSCMProvider(model, commandCenter);
const contentProvider = new GitContentProvider(git, rootPath, onGitChange);
const checkoutStatusBar = new CheckoutStatusBar(model);
const syncStatusBar = new SyncStatusBar(model);
const autoFetcher = new AutoFetcher(model);
const mergeDecorator = new MergeDecorator(model);
const commitHandler = new CommitHandler(model);
disposables.push(
commitHandler,
commandCenter,
provider,
contentProvider,
@ -60,8 +61,7 @@ async function init(disposables: Disposable[]): Promise<void> {
checkoutStatusBar,
syncStatusBar,
autoFetcher,
mergeDecorator,
commitHandler
mergeDecorator
);
}

View file

@ -21,10 +21,6 @@ export class GitSCMProvider implements SCMProvider {
scm.registerSCMProvider('git', this);
}
commit(message: string): Thenable<void> {
return this.commandCenter.commit(message);
}
open(resource: Resource): ProviderResult<void> {
return this.commandCenter.open(resource);
}

View file

@ -5,13 +5,14 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModel, IEditorOptions, IDimension } from 'vs/editor/common/editorCommon';
import { memoize } from 'vs/base/common/decorators';
import { EditorAction, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
import { IEditorContributionCtor } from 'vs/editor/browser/editorBrowser';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { MenuPreventer } from 'vs/editor/contrib/multicursor/browser/menuPreventer';
@ -26,6 +27,8 @@ import { IThemeService } from 'vs/workbench/services/themes/common/themeService'
import URI from 'vs/base/common/uri';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
class SCMCodeEditorWidget extends CodeEditorWidget {
@ -57,7 +60,9 @@ class SCMCodeEditorWidget extends CodeEditorWidget {
}
}
export class SCMEditor {
export const InSCMInputContextKey = new RawContextKey<boolean>('inSCMInput', false);
export class SCMEditor implements ITextModelContentProvider {
private editor: SCMCodeEditorWidget;
private model: IModel;
@ -99,14 +104,28 @@ export class SCMEditor {
constructor(
container: HTMLElement,
@IThemeService private themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
@IModelService private modelService: IModelService
@IInstantiationService instantiationService: IInstantiationService,
@IModelService private modelService: IModelService,
@IContextKeyService private contextKeyService: IContextKeyService,
@ITextModelResolverService private textModelResolverService: ITextModelResolverService
) {
this.editor = this.instantiationService.createInstance(SCMCodeEditorWidget, container, this.editorOptions);
this.model = this.modelService.createModel('', null, URI.parse(`scm:input`));
this.editor.setModel(this.model);
textModelResolverService.registerTextModelContentProvider('scm', this);
const scopedContextKeyService = this.contextKeyService.createScoped(container);
InSCMInputContextKey.bindTo(scopedContextKeyService).set(true);
this.disposables.push(scopedContextKeyService);
const services = new ServiceCollection();
services.set(IContextKeyService, scopedContextKeyService);
const scopedInstantiationService = instantiationService.createChild(services);
this.editor = scopedInstantiationService.createInstance(SCMCodeEditorWidget, container, this.editorOptions);
this.themeService.onDidColorThemeChange(e => this.editor.updateOptions(this.editorOptions), null, this.disposables);
textModelResolverService.createModelReference(URI.parse('scm:input')).done(ref => {
this.model = ref.object.textEditorModel;
this.editor.setModel(this.model);
});
}
get lineHeight(): number {
@ -115,6 +134,10 @@ export class SCMEditor {
// TODO@joao TODO@alex isn't there a better way to get the number of lines?
get lineCount(): number {
if (!this.model) {
return 0;
}
const modelLength = this.model.getValueLength();
const lastPosition = this.model.getPositionAt(modelLength);
const lastLineTop = this.editor.getTopForPosition(lastPosition.lineNumber, lastPosition.column);
@ -131,6 +154,14 @@ export class SCMEditor {
this.editor.focus();
}
provideTextContent(resource: URI): TPromise<IModel> {
if (resource.toString() !== 'scm:input') {
return TPromise.as(null);
}
return TPromise.as(this.modelService.createModel('', null, resource));
}
dispose(): void {
this.disposables = dispose(this.disposables);
}

View file

@ -7,7 +7,6 @@
import 'vs/css!./media/scmViewlet';
import { localize } from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { chain } from 'vs/base/common/event';
import { Throttler } from 'vs/base/common/async';
@ -192,8 +191,6 @@ class CommitAction extends Action {
export class SCMViewlet extends Viewlet {
private static readonly ACCEPT_KEYBINDING = platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter';
private cachedDimension: Dimension;
private editor: SCMEditor;
private listContainer: HTMLElement;
@ -319,13 +316,6 @@ export class SCMViewlet extends Viewlet {
this.editor.focus();
}
private acceptThrottler = new Throttler();
private accept(): void {
// this.acceptThrottler
// .queue(() => this.scmService.activeProvider.commit(this.inputBox.value))
// .done(() => this.inputBox.value = '', err => this.messageService.show(Severity.Error, err));
}
private open(e: ISCMResource): void {
this.scmService.activeProvider.open(e);
}