From ba49fd8bae956e5e0f9f3d17d1686c711209c5d2 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 27 Jan 2017 19:03:47 +0100 Subject: [PATCH 1/6] wip: editor as commit box --- .../electron-browser/workbench.main.ts | 4 +- .../dirtydiffDecorator.ts | 0 .../media/check-inverse.svg | 0 .../media/check.svg | 0 .../media/diff.svg | 0 .../media/dirtydiffDecorator.css | 0 .../media/scmViewlet.css | 4 - .../scm.contribution.ts | 4 +- .../scmActivity.ts | 0 .../parts/scm/electron-browser/scmEditor.ts | 50 +++++++++++ .../{browser => electron-browser}/scmMenus.ts | 0 .../scmViewlet.ts | 90 +++++++++++++------ 12 files changed, 119 insertions(+), 33 deletions(-) rename src/vs/workbench/parts/scm/{browser => electron-browser}/dirtydiffDecorator.ts (100%) rename src/vs/workbench/parts/scm/{browser => electron-browser}/media/check-inverse.svg (100%) rename src/vs/workbench/parts/scm/{browser => electron-browser}/media/check.svg (100%) rename src/vs/workbench/parts/scm/{browser => electron-browser}/media/diff.svg (100%) rename src/vs/workbench/parts/scm/{browser => electron-browser}/media/dirtydiffDecorator.css (100%) rename src/vs/workbench/parts/scm/{browser => electron-browser}/media/scmViewlet.css (95%) rename src/vs/workbench/parts/scm/{browser => electron-browser}/scm.contribution.ts (97%) rename src/vs/workbench/parts/scm/{browser => electron-browser}/scmActivity.ts (100%) create mode 100644 src/vs/workbench/parts/scm/electron-browser/scmEditor.ts rename src/vs/workbench/parts/scm/{browser => electron-browser}/scmMenus.ts (100%) rename src/vs/workbench/parts/scm/{browser => electron-browser}/scmViewlet.ts (78%) diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index d5e22d521d2..65e39ca0afe 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -43,8 +43,8 @@ import 'vs/workbench/parts/search/browser/search.contribution'; import 'vs/workbench/parts/search/browser/searchViewlet'; // can be packaged separately import 'vs/workbench/parts/search/browser/openAnythingHandler'; // can be packaged separately -import 'vs/workbench/parts/scm/browser/scm.contribution'; -import 'vs/workbench/parts/scm/browser/scmViewlet'; // can be packaged separately +import 'vs/workbench/parts/scm/electron-browser/scm.contribution'; +import 'vs/workbench/parts/scm/electron-browser/scmViewlet'; // can be packaged separately import 'vs/workbench/parts/git/electron-browser/git.contribution'; import 'vs/workbench/parts/git/browser/gitQuickOpen'; diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts similarity index 100% rename from src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts rename to src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts diff --git a/src/vs/workbench/parts/scm/browser/media/check-inverse.svg b/src/vs/workbench/parts/scm/electron-browser/media/check-inverse.svg similarity index 100% rename from src/vs/workbench/parts/scm/browser/media/check-inverse.svg rename to src/vs/workbench/parts/scm/electron-browser/media/check-inverse.svg diff --git a/src/vs/workbench/parts/scm/browser/media/check.svg b/src/vs/workbench/parts/scm/electron-browser/media/check.svg similarity index 100% rename from src/vs/workbench/parts/scm/browser/media/check.svg rename to src/vs/workbench/parts/scm/electron-browser/media/check.svg diff --git a/src/vs/workbench/parts/scm/browser/media/diff.svg b/src/vs/workbench/parts/scm/electron-browser/media/diff.svg similarity index 100% rename from src/vs/workbench/parts/scm/browser/media/diff.svg rename to src/vs/workbench/parts/scm/electron-browser/media/diff.svg diff --git a/src/vs/workbench/parts/scm/browser/media/dirtydiffDecorator.css b/src/vs/workbench/parts/scm/electron-browser/media/dirtydiffDecorator.css similarity index 100% rename from src/vs/workbench/parts/scm/browser/media/dirtydiffDecorator.css rename to src/vs/workbench/parts/scm/electron-browser/media/dirtydiffDecorator.css diff --git a/src/vs/workbench/parts/scm/browser/media/scmViewlet.css b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css similarity index 95% rename from src/vs/workbench/parts/scm/browser/media/scmViewlet.css rename to src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css index cdef1d74c0b..fa7b6938cd1 100644 --- a/src/vs/workbench/parts/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css @@ -17,10 +17,6 @@ max-height: 134px; } -.scm-viewlet > .scm-commit-box.scroll > .monaco-inputbox > .wrapper > textarea.input { - overflow-y: scroll; -} - .scm-viewlet .monaco-list-row { padding: 0 12px 0 20px; line-height: 22px; diff --git a/src/vs/workbench/parts/scm/browser/scm.contribution.ts b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts similarity index 97% rename from src/vs/workbench/parts/scm/browser/scm.contribution.ts rename to src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts index 959d1d1dfbe..ab6b871eb79 100644 --- a/src/vs/workbench/parts/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -21,7 +21,7 @@ import { ISCMService } from 'vs/workbench/services/scm/common/scm'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { StatusUpdater } from './scmActivity'; -import SCMPreview, { DisableSCMPreviewAction, EnableSCMPreviewAction } from 'vs/workbench/parts/scm/browser/scmPreview'; +import SCMPreview, { DisableSCMPreviewAction, EnableSCMPreviewAction } from '../browser/scmPreview'; class OpenSCMViewletAction extends ToggleViewletAction { @@ -64,7 +64,7 @@ Registry.as(WorkbenchExtensions.Workbench) if (SCMPreview.enabled) { const viewletDescriptor = new ViewletDescriptor( - 'vs/workbench/parts/scm/browser/scmViewlet', + 'vs/workbench/parts/scm/electron-browser/scmViewlet', 'SCMViewlet', VIEWLET_ID, localize('scm', "SCM"), diff --git a/src/vs/workbench/parts/scm/browser/scmActivity.ts b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts similarity index 100% rename from src/vs/workbench/parts/scm/browser/scmActivity.ts rename to src/vs/workbench/parts/scm/electron-browser/scmActivity.ts diff --git a/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts b/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts new file mode 100644 index 00000000000..2ceaf0cde2a --- /dev/null +++ b/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IEditorOptions } from 'vs/editor/common/editorCommon'; +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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { MenuPreventer } from 'vs/editor/contrib/multicursor/browser/menuPreventer'; +import { SelectionClipboard } from 'vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard'; +import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu'; +import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; +import { SnippetController } from 'vs/editor/contrib/snippet/common/snippetController'; +import { TabCompletionController } from 'vs/editor/contrib/suggest/browser/tabCompletion'; + +export class SCMCommitEditor extends CodeEditorWidget { + + constructor( + domElement: HTMLElement, + options: IEditorOptions, + @IInstantiationService instantiationService: IInstantiationService, + @ICodeEditorService codeEditorService: ICodeEditorService, + @ICommandService commandService: ICommandService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService); + } + + protected _getContributions(): IEditorContributionCtor[] { + return [ + MenuPreventer, + SelectionClipboard, + ContextMenuController, + SuggestController, + SnippetController, + TabCompletionController, + ]; + } + + protected _getActions(): EditorAction[] { + return CommonEditorRegistry.getEditorActions(); + } +} diff --git a/src/vs/workbench/parts/scm/browser/scmMenus.ts b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts similarity index 100% rename from src/vs/workbench/parts/scm/browser/scmMenus.ts rename to src/vs/workbench/parts/scm/electron-browser/scmMenus.ts diff --git a/src/vs/workbench/parts/scm/browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts similarity index 78% rename from src/vs/workbench/parts/scm/browser/scmViewlet.ts rename to src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 1008d1152b7..9868c1de50c 100644 --- a/src/vs/workbench/parts/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -8,6 +8,7 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import * as platform from 'vs/base/common/platform'; +import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { chain } from 'vs/base/common/event'; import { Throttler } from 'vs/base/common/async'; @@ -38,6 +39,10 @@ import { SCMMenus } from './scmMenus'; import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { isDarkTheme } from 'vs/platform/theme/common/themes'; +import { IEditorOptions, IReadOnlyModel, EditorContextKeys, ICommonCodeEditor } from 'vs/editor/common/editorCommon'; +import { SCMCommitEditor } from './scmEditor'; +import { IModel } from 'vs/editor/common/editorCommon'; +import { IModelService } from 'vs/editor/common/services/modelService'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -193,11 +198,13 @@ class CommitAction extends Action { export class SCMViewlet extends Viewlet { - private static ACCEPT_KEYBINDING = platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'; + private static readonly ACCEPT_KEYBINDING = platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'; + private static readonly EditorLineHeight = 22; private cachedDimension: Dimension; private inputBoxContainer: HTMLElement; - private inputBox: InputBox; + private inputEditor: SCMCommitEditor; + private inputModel: IModel; private listContainer: HTMLElement; private list: List; private menus: SCMMenus; @@ -214,7 +221,8 @@ export class SCMViewlet extends Viewlet { @IMessageService private messageService: IMessageService, @IContextMenuService private contextMenuService: IContextMenuService, @IThemeService private themeService: IThemeService, - @IMenuService private menuService: IMenuService + @IMenuService private menuService: IMenuService, + @IModelService private modelService: IModelService ) { super(VIEWLET_ID, telemetryService); @@ -242,20 +250,45 @@ export class SCMViewlet extends Viewlet { const root = parent.getHTMLElement(); this.inputBoxContainer = append(root, $('.scm-commit-box')); - this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { - placeholder: localize('accept', "Message (press {0} to submit)", SCMViewlet.ACCEPT_KEYBINDING), - ariaLabel: localize('acceptAria', "Changes: Type message and press {0} to accept the changes", SCMViewlet.ACCEPT_KEYBINDING), - flexibleHeight: true - }); + const opts: IEditorOptions = { + wrappingColumn: 0, + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + selectOnLineNumbers: false, + selectionHighlight: false, + scrollbar: { + horizontal: 'hidden' + }, + lineDecorationsWidth: 0, + scrollBeyondLastLine: false, + theme: this.themeService.getColorTheme().id, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + acceptSuggestionOnEnter: false, + lineHeight: SCMViewlet.EditorLineHeight, + wordWrap: true + }; - 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.accept, this, this.disposables); + this.inputEditor = this.instantiationService.createInstance(SCMCommitEditor, this.inputBoxContainer, opts); - chain(this.inputBox.onDidHeightChange) - .map(() => this.cachedDimension) - .on(this.layout, this, this.disposables); + + this.inputModel = this.modelService.createModel('', null, URI.parse(`scm:input`)); + this.inputEditor.setModel(this.inputModel); + + this.disposables.push(this.inputModel.onDidChangeContent(() => this.layout())); + + // this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { + // placeholder: localize('accept', "Message (press {0} to submit)", SCMViewlet.ACCEPT_KEYBINDING), + // ariaLabel: localize('acceptAria', "Changes: Type message and press {0} to accept the changes", SCMViewlet.ACCEPT_KEYBINDING), + // flexibleHeight: true + // }); + + // 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.accept, this, this.disposables); this.listContainer = append(root, $('.scm-status.show-file-icons')); const delegate = new Delegate(); @@ -273,7 +306,7 @@ export class SCMViewlet extends Viewlet { .on(this.open, this, this.disposables); this.list.onContextMenu(this.onListContextMenu, this, this.disposables); - this.disposables.push(this.inputBox, this.list); + this.disposables.push(this.inputEditor, this.list); this.setActiveProvider(this.scmService.activeProvider); this.scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); @@ -303,13 +336,20 @@ export class SCMViewlet extends Viewlet { } this.cachedDimension = dimension; - this.inputBox.layout(); - const listHeight = dimension.height - (this.inputBox.height + 12 /* margin */); + // TODO@joao TODO@alex isn't there a better way to get the total view height? + const modelLength = this.inputModel.getValueLength(); + const lastPosition = this.inputModel.getPositionAt(modelLength); + const lastLineTop = this.inputEditor.getTopForPosition(lastPosition.lineNumber, lastPosition.column); + const viewHeight = lastLineTop + SCMViewlet.EditorLineHeight; + + const lineCount = viewHeight / SCMViewlet.EditorLineHeight; + const inputEditorHeight = Math.min(lineCount, 8) * SCMViewlet.EditorLineHeight; + this.inputEditor.layout({ width: dimension.width - 25, height: inputEditorHeight }); + + const listHeight = dimension.height - (inputEditorHeight + 12 /* margin */); this.listContainer.style.height = `${listHeight}px`; this.list.layout(listHeight); - - toggleClass(this.inputBoxContainer, 'scroll', this.inputBox.height >= 134); } getOptimalWidth(): number { @@ -318,14 +358,14 @@ export class SCMViewlet extends Viewlet { focus(): void { super.focus(); - this.inputBox.focus(); + this.inputEditor.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)); + // 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 { @@ -334,7 +374,7 @@ export class SCMViewlet extends Viewlet { getActions(): IAction[] { return [ - this.instantiationService.createInstance(CommitAction, this.inputBox), + // this.instantiationService.createInstance(CommitAction, this.inputBox), ...this.menus.getTitleActions() ]; } From 1e99f8d8f40ae1fae67e411588a3d0a845fa78c0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 30 Jan 2017 19:45:24 +0000 Subject: [PATCH 2/6] extract scm editor --- .../scm/electron-browser/media/scmViewlet.css | 6 +- .../parts/scm/electron-browser/scmEditor.ts | 90 ++++++++++++++++++- .../parts/scm/electron-browser/scmViewlet.ts | 64 +++---------- 3 files changed, 100 insertions(+), 60 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css index fa7b6938cd1..8f2b7fcd0e3 100644 --- a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css +++ b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css @@ -9,14 +9,10 @@ background-position: center; } -.scm-viewlet > .scm-commit-box { +.scm-viewlet > .scm-editor { padding: 5px 9px 5px 16px; } -.scm-viewlet > .scm-commit-box > .monaco-inputbox > .wrapper > .mirror { - max-height: 134px; -} - .scm-viewlet .monaco-list-row { padding: 0 12px 0 20px; line-height: 22px; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts b/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts index 2ceaf0cde2a..5cfa7c06e42 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts @@ -5,7 +5,8 @@ 'use strict'; -import { IEditorOptions } from 'vs/editor/common/editorCommon'; +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'; @@ -19,8 +20,14 @@ import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/con import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { SnippetController } from 'vs/editor/contrib/snippet/common/snippetController'; import { TabCompletionController } from 'vs/editor/contrib/suggest/browser/tabCompletion'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; +import { append, $ } from 'vs/base/browser/dom'; +import URI from 'vs/base/common/uri'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import Event, { Emitter } from 'vs/base/common/event'; -export class SCMCommitEditor extends CodeEditorWidget { +class SCMCodeEditorWidget extends CodeEditorWidget { constructor( domElement: HTMLElement, @@ -48,3 +55,82 @@ export class SCMCommitEditor extends CodeEditorWidget { return CommonEditorRegistry.getEditorActions(); } } + +export class SCMEditor { + + private editor: SCMCodeEditorWidget; + private model: IModel; + private disposables: IDisposable[] = []; + + @memoize + get onDidChangeContent(): Event { + let listener: IDisposable; + const emitter = new Emitter({ + onFirstListenerAdd: () => listener = this.model.onDidChangeContent(() => emitter.fire()), + onLastListenerRemove: () => dispose(listener) + }); + + return emitter.event; + } + + private get editorOptions(): IEditorOptions { + return { + wrappingColumn: 0, + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + selectOnLineNumbers: false, + selectionHighlight: false, + scrollbar: { + horizontal: 'hidden' + }, + lineDecorationsWidth: 0, + scrollBeyondLastLine: false, + theme: this.themeService.getColorTheme().id, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + acceptSuggestionOnEnter: false, + wordWrap: true + }; + } + + constructor( + container: HTMLElement, + @IThemeService private themeService: IThemeService, + @IInstantiationService private instantiationService: IInstantiationService, + @IModelService private modelService: IModelService + ) { + this.editor = this.instantiationService.createInstance(SCMCodeEditorWidget, container, this.editorOptions); + this.model = this.modelService.createModel('', null, URI.parse(`scm:input`)); + this.editor.setModel(this.model); + + this.themeService.onDidColorThemeChange(e => this.editor.updateOptions(this.editorOptions), null, this.disposables); + } + + get lineHeight(): number { + return this.editor.getConfiguration().lineHeight; + } + + // TODO@joao TODO@alex isn't there a better way to get the number of lines? + get lineCount(): number { + const modelLength = this.model.getValueLength(); + const lastPosition = this.model.getPositionAt(modelLength); + const lastLineTop = this.editor.getTopForPosition(lastPosition.lineNumber, lastPosition.column); + const viewHeight = lastLineTop + this.lineHeight; + + return viewHeight / this.lineHeight; + } + + layout(dimension: IDimension): void { + this.editor.layout(dimension); + } + + focus(): void { + this.editor.focus(); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 9868c1de50c..0befe3f94fe 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -8,11 +8,9 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import * as platform from 'vs/base/common/platform'; -import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { chain } from 'vs/base/common/event'; import { Throttler } from 'vs/base/common/async'; -import { domEvent } from 'vs/base/browser/event'; import { IDisposable, dispose, empty as EmptyDisposable } from 'vs/base/common/lifecycle'; import { Builder, Dimension } from 'vs/base/browser/builder'; import { Viewlet } from 'vs/workbench/browser/viewlet'; @@ -30,8 +28,6 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { Action, IAction, IActionItem } from 'vs/base/common/actions'; import { createActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; @@ -39,9 +35,7 @@ import { SCMMenus } from './scmMenus'; import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { isDarkTheme } from 'vs/platform/theme/common/themes'; -import { IEditorOptions, IReadOnlyModel, EditorContextKeys, ICommonCodeEditor } from 'vs/editor/common/editorCommon'; -import { SCMCommitEditor } from './scmEditor'; -import { IModel } from 'vs/editor/common/editorCommon'; +import { SCMEditor } from './scmEditor'; import { IModelService } from 'vs/editor/common/services/modelService'; interface SearchInputEvent extends Event { @@ -199,12 +193,9 @@ class CommitAction extends Action { export class SCMViewlet extends Viewlet { private static readonly ACCEPT_KEYBINDING = platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'; - private static readonly EditorLineHeight = 22; private cachedDimension: Dimension; - private inputBoxContainer: HTMLElement; - private inputEditor: SCMCommitEditor; - private inputModel: IModel; + private editor: SCMEditor; private listContainer: HTMLElement; private list: List; private menus: SCMMenus; @@ -248,36 +239,11 @@ export class SCMViewlet extends Viewlet { parent.addClass('scm-viewlet'); const root = parent.getHTMLElement(); - this.inputBoxContainer = append(root, $('.scm-commit-box')); + const editorContainer = append(root, $('.scm-editor')); - const opts: IEditorOptions = { - wrappingColumn: 0, - overviewRulerLanes: 0, - glyphMargin: false, - lineNumbers: 'off', - folding: false, - selectOnLineNumbers: false, - selectionHighlight: false, - scrollbar: { - horizontal: 'hidden' - }, - lineDecorationsWidth: 0, - scrollBeyondLastLine: false, - theme: this.themeService.getColorTheme().id, - renderLineHighlight: 'none', - fixedOverflowWidgets: true, - acceptSuggestionOnEnter: false, - lineHeight: SCMViewlet.EditorLineHeight, - wordWrap: true - }; - - this.inputEditor = this.instantiationService.createInstance(SCMCommitEditor, this.inputBoxContainer, opts); - - - this.inputModel = this.modelService.createModel('', null, URI.parse(`scm:input`)); - this.inputEditor.setModel(this.inputModel); - - this.disposables.push(this.inputModel.onDidChangeContent(() => this.layout())); + this.editor = this.instantiationService.createInstance(SCMEditor, editorContainer); + this.editor.onDidChangeContent(() => this.layout(), null, this.disposables); + this.disposables.push(this.editor); // this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { // placeholder: localize('accept', "Message (press {0} to submit)", SCMViewlet.ACCEPT_KEYBINDING), @@ -306,7 +272,7 @@ export class SCMViewlet extends Viewlet { .on(this.open, this, this.disposables); this.list.onContextMenu(this.onListContextMenu, this, this.disposables); - this.disposables.push(this.inputEditor, this.list); + this.disposables.push(this.list); this.setActiveProvider(this.scmService.activeProvider); this.scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); @@ -323,7 +289,6 @@ export class SCMViewlet extends Viewlet { return; } - const elements = provider.resources .reduce<(ISCMResourceGroup | ISCMResource)[]>((r, g) => [...r, g, ...g.resources], []); @@ -337,17 +302,10 @@ export class SCMViewlet extends Viewlet { this.cachedDimension = dimension; - // TODO@joao TODO@alex isn't there a better way to get the total view height? - const modelLength = this.inputModel.getValueLength(); - const lastPosition = this.inputModel.getPositionAt(modelLength); - const lastLineTop = this.inputEditor.getTopForPosition(lastPosition.lineNumber, lastPosition.column); - const viewHeight = lastLineTop + SCMViewlet.EditorLineHeight; + const editorHeight = Math.min(this.editor.lineCount, 8) * this.editor.lineHeight; + this.editor.layout({ width: dimension.width - 25, height: editorHeight }); - const lineCount = viewHeight / SCMViewlet.EditorLineHeight; - const inputEditorHeight = Math.min(lineCount, 8) * SCMViewlet.EditorLineHeight; - this.inputEditor.layout({ width: dimension.width - 25, height: inputEditorHeight }); - - const listHeight = dimension.height - (inputEditorHeight + 12 /* margin */); + const listHeight = dimension.height - (editorHeight + 12 /* margin */); this.listContainer.style.height = `${listHeight}px`; this.list.layout(listHeight); } @@ -358,7 +316,7 @@ export class SCMViewlet extends Viewlet { focus(): void { super.focus(); - this.inputEditor.focus(); + this.editor.focus(); } private acceptThrottler = new Throttler(); From e43137f514ce0e8f840b61d051a161dfcfb3d0a7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 30 Jan 2017 20:17:02 +0000 Subject: [PATCH 3/6] long commit decoration --- extensions/git/src/commit.ts | 50 +++++++++++++++++++ extensions/git/src/main.ts | 5 +- src/vs/editor/contrib/hover/browser/hover.ts | 2 +- .../parts/scm/electron-browser/scmEditor.ts | 3 +- 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 extensions/git/src/commit.ts diff --git a/extensions/git/src/commit.ts b/extensions/git/src/commit.ts new file mode 100644 index 00000000000..6d471729774 --- /dev/null +++ b/extensions/git/src/commit.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { workspace, languages, Disposable, Uri, TextDocumentChangeEvent, DiagnosticCollection, Diagnostic, DiagnosticSeverity } from 'vscode'; +import { Model } from './model'; +import { filterEvent } from './util'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + +function isSCMInput(uri: Uri) { + return uri.toString() === 'scm:input'; +} + +// TODO@Joao: prevent these diagnostics from showing in global error/warnings +// TODO@Joao: hover dissapears if editor is scrolled +export class CommitHandler { + + private diagnosticCollection: DiagnosticCollection; + private disposables: Disposable[] = []; + + constructor(private model: Model) { + const onDidChange = filterEvent(workspace.onDidChangeTextDocument, e => e.document && isSCMInput(e.document.uri)); + onDidChange(this.onSCMInputChange, this, this.disposables); + + this.diagnosticCollection = languages.createDiagnosticCollection(localize('git commit message', "Git Commit Message")); + this.disposables.push(this.diagnosticCollection); + } + + private onSCMInputChange(e: TextDocumentChangeEvent): void { + const uri = e.document.uri; + const firstLineRange = e.document.lineAt(0).range; + const firstLineLength = firstLineRange.end.character - firstLineRange.start.character; + + if (firstLineLength > 80) { + const warning = new Diagnostic(firstLineRange, localize('too long', "You should keep the first line under 50 characters.\nYou can use more lines for extra information."), DiagnosticSeverity.Warning); + this.diagnosticCollection.set(uri, [warning]); + } else { + this.diagnosticCollection.clear(); + } + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } +} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 1df0364a6ad..a724582c0ed 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -15,6 +15,7 @@ import { filterEvent, anyEvent } from './util'; import { GitContentProvider } from './contentProvider'; import { AutoFetcher } from './autofetch'; import { MergeDecorator } from './merge'; +import { CommitHandler } from './commit'; import * as nls from 'vscode-nls'; const localize = nls.config()(); @@ -48,6 +49,7 @@ async function init(disposables: Disposable[]): Promise { const syncStatusBar = new SyncStatusBar(model); const autoFetcher = new AutoFetcher(model); const mergeDecorator = new MergeDecorator(model); + const commitHandler = new CommitHandler(model); disposables.push( commandCenter, @@ -58,7 +60,8 @@ async function init(disposables: Disposable[]): Promise { checkoutStatusBar, syncStatusBar, autoFetcher, - mergeDecorator + mergeDecorator, + commitHandler ); } diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index b2be03752c8..dc3e994478c 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -24,7 +24,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import EditorContextKeys = editorCommon.EditorContextKeys; @editorContribution -class ModesHoverController implements editorCommon.IEditorContribution { +export class ModesHoverController implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.hover'; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts b/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts index 5cfa7c06e42..dda97403401 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts @@ -20,9 +20,9 @@ import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/con import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { SnippetController } from 'vs/editor/contrib/snippet/common/snippetController'; import { TabCompletionController } from 'vs/editor/contrib/suggest/browser/tabCompletion'; +import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; -import { append, $ } from 'vs/base/browser/dom'; import URI from 'vs/base/common/uri'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; @@ -48,6 +48,7 @@ class SCMCodeEditorWidget extends CodeEditorWidget { SuggestController, SnippetController, TabCompletionController, + ModesHoverController ]; } From 418744c1d8e0d854f31848b6ce4947571791ef89 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 31 Jan 2017 09:59:21 +0100 Subject: [PATCH 4/6] git: improve CommitHandler --- extensions/git/src/commit.ts | 69 ++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/extensions/git/src/commit.ts b/extensions/git/src/commit.ts index 6d471729774..8ec8fd2a1e3 100644 --- a/extensions/git/src/commit.ts +++ b/extensions/git/src/commit.ts @@ -5,7 +5,7 @@ 'use strict'; -import { workspace, languages, Disposable, Uri, TextDocumentChangeEvent, DiagnosticCollection, Diagnostic, DiagnosticSeverity } from 'vscode'; +import { workspace, window, languages, Disposable, Uri, TextDocumentChangeEvent, HoverProvider, Hover, TextEditor, Position, TextDocument, Range, TextEditorDecorationType } from 'vscode'; import { Model } from './model'; import { filterEvent } from './util'; import * as nls from 'vscode-nls'; @@ -16,32 +16,71 @@ function isSCMInput(uri: Uri) { return uri.toString() === 'scm:input'; } -// TODO@Joao: prevent these diagnostics from showing in global error/warnings -// TODO@Joao: hover dissapears if editor is scrolled -export class CommitHandler { +interface Diagnostic { + range: Range; + message: string; +} - private diagnosticCollection: DiagnosticCollection; +// TODO@Joao: hover dissapears if editor is scrolled +export class CommitHandler implements HoverProvider { + + private visibleTextEditorsDisposable: Disposable; + private editor: TextEditor; + private diagnostics: Diagnostic[] = []; + private decorationType: TextEditorDecorationType; private disposables: Disposable[] = []; constructor(private model: Model) { + this.visibleTextEditorsDisposable = window.onDidChangeVisibleTextEditors(this.onVisibleTextEditors, this); + this.onVisibleTextEditors(window.visibleTextEditors); + + this.decorationType = window.createTextEditorDecorationType({ + isWholeLine: true, + color: 'rgb(228, 157, 43)', + dark: { + color: 'rgb(220, 211, 71)' + } + }); + } + + private onVisibleTextEditors(editors: TextEditor[]): void { + const [editor] = editors.filter(e => isSCMInput(e.document.uri)); + + if (!editor) { + return; + } + + this.visibleTextEditorsDisposable.dispose(); + this.editor = editor; + const onDidChange = filterEvent(workspace.onDidChangeTextDocument, e => e.document && isSCMInput(e.document.uri)); onDidChange(this.onSCMInputChange, this, this.disposables); - this.diagnosticCollection = languages.createDiagnosticCollection(localize('git commit message', "Git Commit Message")); - this.disposables.push(this.diagnosticCollection); + languages.registerHoverProvider({ scheme: 'scm' }, this); } private onSCMInputChange(e: TextDocumentChangeEvent): void { - const uri = e.document.uri; - const firstLineRange = e.document.lineAt(0).range; - const firstLineLength = firstLineRange.end.character - firstLineRange.start.character; + this.diagnostics = []; - if (firstLineLength > 80) { - const warning = new Diagnostic(firstLineRange, localize('too long', "You should keep the first line under 50 characters.\nYou can use more lines for extra information."), DiagnosticSeverity.Warning); - this.diagnosticCollection.set(uri, [warning]); - } else { - this.diagnosticCollection.clear(); + const range = e.document.lineAt(0).range; + const length = range.end.character - range.start.character; + + if (length > 80) { + const message = localize('too long', "You should keep the first line under 50 characters.\n\nYou can use more lines for extra information."); + this.diagnostics.push({ range, message }); } + + this.editor.setDecorations(this.decorationType, this.diagnostics.map(d => d.range)); + } + + provideHover(document: TextDocument, position: Position): Hover | undefined { + const [decoration] = this.diagnostics.filter(d => d.range.contains(position)); + + if (!decoration) { + return; + } + + return new Hover(decoration.message, decoration.range); } dispose(): void { From cb36e6c21725ae4d476b8a526b9b5298fd8e7860 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 31 Jan 2017 11:15:52 +0100 Subject: [PATCH 5/6] git: commit --- extensions/git/package.json | 13 +++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 49 ++++++++++++++++--- extensions/git/src/commit.ts | 29 +++++++++-- extensions/git/src/main.ts | 10 ++-- extensions/git/src/scmProvider.ts | 4 -- .../parts/scm/electron-browser/scmEditor.ts | 45 ++++++++++++++--- .../parts/scm/electron-browser/scmViewlet.ts | 10 ---- 8 files changed, 126 insertions(+), 35 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 03e4423cd88..bd055ca12ee 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -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": [ { diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 01aa344df75..ea998eabbb9 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -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", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 83ca45aa613..5185a6155b8 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -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 { + private async _commit(fn: () => Promise): Promise { + 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 { + 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 { + const didCommit = await this._commit(async () => this.commitController.message); + + if (didCommit) { + this.commitController.message = ''; + } } @CommandCenter.Command('git.commitStaged') diff --git a/extensions/git/src/commit.ts b/extensions/git/src/commit.ts index 8ec8fd2a1e3..7010838f6f7 100644 --- a/extensions/git/src/commit.ts +++ b/extensions/git/src/commit.ts @@ -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); diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index a724582c0ed..61b145296be 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -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 { 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 { checkoutStatusBar, syncStatusBar, autoFetcher, - mergeDecorator, - commitHandler + mergeDecorator ); } diff --git a/extensions/git/src/scmProvider.ts b/extensions/git/src/scmProvider.ts index 925f7f0ef3e..f06078c31bc 100644 --- a/extensions/git/src/scmProvider.ts +++ b/extensions/git/src/scmProvider.ts @@ -21,10 +21,6 @@ export class GitSCMProvider implements SCMProvider { scm.registerSCMProvider('git', this); } - commit(message: string): Thenable { - return this.commandCenter.commit(message); - } - open(resource: Resource): ProviderResult { return this.commandCenter.open(resource); } diff --git a/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts b/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts index dda97403401..a2fb66de18f 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmEditor.ts @@ -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('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 { + if (resource.toString() !== 'scm:input') { + return TPromise.as(null); + } + + return TPromise.as(this.modelService.createModel('', null, resource)); + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 0befe3f94fe..b5c1a5477f4 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -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); } From e70f9eeb250750090e439ff02d66c2bb9ec939a7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 31 Jan 2017 11:16:38 +0100 Subject: [PATCH 6/6] scm: remove commit hack --- .../parts/scm/electron-browser/scmViewlet.ts | 58 +------------------ 1 file changed, 3 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index b5c1a5477f4..af019e77619 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -6,10 +6,8 @@ 'use strict'; import 'vs/css!./media/scmViewlet'; -import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { chain } from 'vs/base/common/event'; -import { Throttler } from 'vs/base/common/async'; import { IDisposable, dispose, empty as EmptyDisposable } from 'vs/base/common/lifecycle'; import { Builder, Dimension } from 'vs/base/browser/builder'; import { Viewlet } from 'vs/workbench/browser/viewlet'; @@ -25,10 +23,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IMessageService, Severity } from 'vs/platform/message/common/message'; -import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IMessageService } from 'vs/platform/message/common/message'; import { IMenuService } from 'vs/platform/actions/common/actions'; -import { Action, IAction, IActionItem } from 'vs/base/common/actions'; +import { IAction, IActionItem } from 'vs/base/common/actions'; import { createActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; import { SCMMenus } from './scmMenus'; import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -143,52 +140,6 @@ class Delegate implements IDelegate { } } -/** - * HACK - */ -class CommitAction extends Action { - - private activeProvider: ISCMProvider; - private isRunning = false; - private throttler = new Throttler(); - private disposables: IDisposable[] = []; - - constructor( - private inputBox: InputBox, - @ISCMService scmService: ISCMService, - @IMessageService private messageService: IMessageService - ) { - super('scm.commit', localize('commit', "Commit"), 'scm-commit'); - - this.setActiveProvider(scmService.activeProvider); - scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); - inputBox.onDidChange(this.updateEnablement, this, this.disposables); - } - - private setActiveProvider(activeProvider: ISCMProvider | undefined): void { - this.activeProvider = activeProvider; - this.updateEnablement(); - } - - private updateEnablement(): void { - this.enabled = !!this.activeProvider && !this.isRunning && !!this.inputBox.value; - } - - run(): TPromise { - return this.throttler - .queue(() => { - this.isRunning = true; - return this.activeProvider.commit(this.inputBox.value); - }) - .then(() => this.inputBox.value = '', err => this.messageService.show(Severity.Error, err)) - .then(() => this.isRunning = false); - } - - dispose(): void { - this.disposables = dispose(this.disposables); - } -} - export class SCMViewlet extends Viewlet { private cachedDimension: Dimension; @@ -321,10 +272,7 @@ export class SCMViewlet extends Viewlet { } getActions(): IAction[] { - return [ - // this.instantiationService.createInstance(CommitAction, this.inputBox), - ...this.menus.getTitleActions() - ]; + return this.menus.getTitleActions(); } getSecondaryActions(): IAction[] {