提交 93013999 编写于 作者: B Benjamin Pasero

Merge branch 'master' into ben/editor

......@@ -27,6 +27,12 @@ export function activate(context: vscode.ExtensionContext): void {
//extensions suggestions
context.subscriptions.push(...registerExtensionsCompletions());
// launch.json variable suggestions
context.subscriptions.push(registerVariableCompletions('**/launch.json'));
// task.json variable suggestions
context.subscriptions.push(registerVariableCompletions('**/tasks.json'));
// launch.json decorations
context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => updateLaunchJsonDecorations(editor), null, context.subscriptions));
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(event => {
......@@ -108,6 +114,27 @@ function registerSettingsCompletions(): vscode.Disposable {
});
}
function registerVariableCompletions(pattern: string): vscode.Disposable {
return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern }, {
provideCompletionItems(document, position, token) {
const location = getLocation(document.getText(), document.offsetAt(position));
if (!location.isAtPropertyKey && location.previousNode && location.previousNode.type === 'string') {
return [{ label: 'workspaceFolder', detail: localize('workspaceFolder', "The path of the folder opened in VS Code") }, { label: 'workspaceFolderBasename', detail: localize('workspaceFolderBasename', "The name of the folder opened in VS Code without any slashes (/)") },
{ label: 'relativeFile', detail: localize('relativeFile', "The current opened file relative to ${workspaceFolder}") }, { label: 'file', detail: localize('file', "The current opened file") }, { label: 'cwd', detail: localize('cwd', "The task runner's current working directory on startup") },
{ label: 'lineNumber', detail: localize('lineNumber', "The current selected line number in the active file") }, { label: 'selectedText', detail: localize('selectedText', "The current selected text in the active file") },
{ label: 'fileDirname', detail: localize('fileDirname', "The current opened file's dirname") }, { label: 'fileExtname', detail: localize('fileExtname', "The current opened file's extension") }, { label: 'fileBasename', detail: localize('fileBasename', "The current opened file's basename") },
{ label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") }].map(variable => ({
label: '${' + variable.label + '}',
range: new vscode.Range(position, position),
detail: variable.detail
}));
}
return [];
}
});
}
interface IExtensionsContent {
recommendations: string[];
}
......
......@@ -367,7 +367,7 @@ export class CodeWindow implements ICodeWindow {
}
// To prevent flashing, we set the window visible after the page has finished to load but before Code is loaded
if (!this._win.isVisible()) {
if (this._win && !this._win.isVisible()) {
if (this.windowState.mode === WindowMode.Maximized) {
this._win.maximize();
}
......
......@@ -57,7 +57,7 @@ export class ColorPickerHeader extends Disposable {
}
private onDidChangePresentation(): void {
this.pickedColorNode.textContent = this.model.presentation.label;
this.pickedColorNode.textContent = this.model.presentation ? this.model.presentation.label : '';
}
}
......
......@@ -26,6 +26,7 @@ class AlternativeKeyEmitter extends Emitter<boolean> {
private _subscriptions: IDisposable[] = [];
private _isPressed: boolean;
private static instance: AlternativeKeyEmitter;
private _suppressAltKeyUp: boolean = false;
private constructor(contextMenuService: IContextMenuService) {
super();
......@@ -33,7 +34,16 @@ class AlternativeKeyEmitter extends Emitter<boolean> {
this._subscriptions.push(domEvent(document.body, 'keydown')(e => {
this.isPressed = e.altKey || ((isWindows || isLinux) && e.shiftKey);
}));
this._subscriptions.push(domEvent(document.body, 'keyup')(e => this.isPressed = false));
this._subscriptions.push(domEvent(document.body, 'keyup')(e => {
if (this.isPressed) {
if (this._suppressAltKeyUp) {
e.preventDefault();
}
}
this._suppressAltKeyUp = false;
this.isPressed = false;
}));
this._subscriptions.push(domEvent(document.body, 'mouseleave')(e => this.isPressed = false));
this._subscriptions.push(domEvent(document.body, 'blur')(e => this.isPressed = false));
// Workaround since we do not get any events while a context menu is shown
......@@ -49,6 +59,12 @@ class AlternativeKeyEmitter extends Emitter<boolean> {
this.fire(this._isPressed);
}
suppressAltKeyUp() {
// Sometimes the native alt behavior needs to be suppresed since the alt was already used as an alternative key
// Example: windows behavior to toggle tha top level menu #44396
this._suppressAltKeyUp = true;
}
static getInstance(contextMenuService: IContextMenuService) {
if (!AlternativeKeyEmitter.instance) {
AlternativeKeyEmitter.instance = new AlternativeKeyEmitter(contextMenuService);
......@@ -149,6 +165,11 @@ export class MenuItemActionItem extends ActionItem {
event.preventDefault();
event.stopPropagation();
const altKey = AlternativeKeyEmitter.getInstance(this._contextMenuService);
if (altKey.isPressed) {
altKey.suppressAltKeyUp();
}
this.actionRunner.run(this._commandAction)
.done(undefined, err => this._notificationService.error(err));
}
......
......@@ -102,6 +102,11 @@ interface InstallableExtension {
metadata?: IGalleryMetadata;
}
enum Operation {
Install = 1,
Update
}
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
_serviceBrand: any;
......@@ -227,19 +232,21 @@ export class ExtensionManagementService extends Disposable implements IExtension
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension> {
this.onInstallExtensions([extension]);
return this.collectExtensionsToInstall(extension)
return this.getInstalled(LocalExtensionType.User)
.then(installed => this.collectExtensionsToInstall(extension)
.then(
extensionsToInstall => {
if (extensionsToInstall.length > 1) {
this.onInstallExtensions(extensionsToInstall.slice(1));
}
const operataions: Operation[] = extensionsToInstall.map(e => this.getOperation(e, installed));
return this.downloadAndInstallExtensions(extensionsToInstall)
.then(
locals => this.onDidInstallExtensions(extensionsToInstall, locals, [])
locals => this.onDidInstallExtensions(extensionsToInstall, locals, operataions, [])
.then(() => locals.filter(l => areSameExtensions({ id: getGalleryExtensionIdFromLocal(l), uuid: l.identifier.uuid }, extension.identifier)[0])),
errors => this.onDidInstallExtensions(extensionsToInstall, [], errors));
errors => this.onDidInstallExtensions(extensionsToInstall, [], operataions, errors));
},
error => this.onDidInstallExtensions([extension], [], [error]));
error => this.onDidInstallExtensions([extension], [], [this.getOperation(extension, installed)], [error])));
}
reinstallFromGallery(extension: ILocalExtension): TPromise<ILocalExtension> {
......@@ -259,6 +266,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private getOperation(extensionToInstall: IGalleryExtension, installed: ILocalExtension[]): Operation {
return installed.some(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, extensionToInstall.identifier)) ? Operation.Update : Operation.Install;
}
private collectExtensionsToInstall(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
return this.galleryService.loadCompatibleVersion(extension)
.then(compatible => {
......@@ -340,7 +351,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
}
private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], errors: Error[]): TPromise<any> {
private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], operations: Operation[], errors: Error[]): TPromise<any> {
extensions.forEach((gallery, index) => {
const identifier = { id: getLocalExtensionIdFromGallery(gallery, gallery.version), uuid: gallery.identifier.uuid };
const local = locals[index];
......@@ -354,7 +365,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode });
}
const startTime = this.installationStartTime.get(gallery.identifier.id);
this.reportTelemetry('extensionGallery:install', getGalleryExtensionTelemetryData(gallery), startTime ? new Date().getTime() - startTime : void 0, error);
this.reportTelemetry(operations[index] === Operation.Install ? 'extensionGallery:install' : 'extensionGallery:update', getGalleryExtensionTelemetryData(gallery), startTime ? new Date().getTime() - startTime : void 0, error);
this.installationStartTime.delete(gallery.identifier.id);
});
return errors.length ? TPromise.wrapError(this.joinErrors(errors)) : TPromise.as(null);
......@@ -892,6 +903,16 @@ export class ExtensionManagementService extends Disposable implements IExtension
]
}
*/
/* __GDPR__
"extensionGallery:update" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
}
}
......
......@@ -33,7 +33,6 @@ export interface IPickOpenEntry {
run?: (context: IEntryRunContext) => void;
action?: IAction;
payload?: any;
picked?: boolean;
}
export interface IPickOpenItem {
......@@ -85,46 +84,6 @@ export interface IPickOptions {
* a context key to set when this picker is active
*/
contextKey?: string;
/**
* an optional flag to make this picker multi-select (honoured by extension API)
*/
canPickMany?: boolean;
}
export interface IInputOptions {
/**
* the value to prefill in the input box
*/
value?: string;
/**
* the selection of value, default to the whole word
*/
valueSelection?: [number, number];
/**
* the text to display underneath the input box
*/
prompt?: string;
/**
* an optional string to show as place holder in the input box to guide the user what to type
*/
placeHolder?: string;
/**
* set to true to show a password prompt that will not show the typed value
*/
password?: boolean;
ignoreFocusLost?: boolean;
/**
* an optional function that is used to validate user input.
*/
validateInput?: (input: string) => TPromise<string>;
}
export interface IShowOptions {
......
......@@ -6,8 +6,83 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
import { IPickOptions, IPickOpenEntry, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
export interface IPickOpenEntry {
id?: string;
label: string;
description?: string;
detail?: string;
picked?: boolean;
}
export interface IQuickNavigateConfiguration {
keybindings: ResolvedKeybinding[];
}
export interface IPickOptions {
/**
* an optional string to show as place holder in the input box to guide the user what she picks on
*/
placeHolder?: string;
/**
* an optional flag to include the description when filtering the picks
*/
matchOnDescription?: boolean;
/**
* an optional flag to include the detail when filtering the picks
*/
matchOnDetail?: boolean;
/**
* an optional flag to not close the picker on focus lost
*/
ignoreFocusLost?: boolean;
/**
* an optional flag to make this picker multi-select
*/
canPickMany?: boolean;
}
export interface IInputOptions {
/**
* the value to prefill in the input box
*/
value?: string;
/**
* the selection of value, default to the whole word
*/
valueSelection?: [number, number];
/**
* the text to display underneath the input box
*/
prompt?: string;
/**
* an optional string to show as place holder in the input box to guide the user what to type
*/
placeHolder?: string;
/**
* set to true to show a password prompt that will not show the typed value
*/
password?: boolean;
ignoreFocusLost?: boolean;
/**
* an optional function that is used to validate user input.
*/
validateInput?: (input: string) => TPromise<string>;
}
export const IQuickInputService = createDecorator<IQuickInputService>('quickInputService');
......@@ -29,7 +104,7 @@ export interface IQuickInputService {
toggle(): void;
navigate(next: boolean): void;
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void;
accept(): TPromise<void>;
......
......@@ -6,8 +6,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { asWinJsPromise } from 'vs/base/common/async';
import { IPickOptions, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IPickOptions, IInputOptions, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { InputBoxOptions } from 'vscode';
import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
......
......@@ -26,7 +26,7 @@ import * as modes from 'vs/editor/common/modes';
import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
import { IConfig, IAdapterExecutable, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
......
......@@ -40,7 +40,6 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
const itemsPromise = <TPromise<Item[]>>TPromise.wrap(itemsOrItemsPromise);
const quickPickWidget = this._proxy.$show({
autoFocus: { autoFocusFirstEntry: true },
placeHolder: options && options.placeHolder,
matchOnDescription: options && options.matchOnDescription,
matchOnDetail: options && options.matchOnDetail,
......
......@@ -74,7 +74,6 @@ export class ActivitybarPart extends Part {
this.globalActivityIdToActions = Object.create(null);
this.placeholderComposites = JSON.parse(this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, '[]'));
this.compositeActions = Object.create(null);
this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
icon: true,
......@@ -91,6 +90,8 @@ export class ActivitybarPart extends Part {
colors: ActivitybarPart.COLORS,
overflowActionSize: ActivitybarPart.ACTION_HEIGHT
});
const previousState = this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, void 0);
this.placeholderComposites = previousState ? JSON.parse(previousState) : this.compositeBar.getCompositesFromStorage().map(id => (<IPlaceholderComposite>{ id, iconUrl: void 0 }));
this.registerListeners();
this.updateCompositebar();
......@@ -325,7 +326,6 @@ export class ActivitybarPart extends Part {
public shutdown(): void {
const state = this.viewletService.getViewlets().filter(viewlet => this.hasRegisteredViews(viewlet)).map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl }));
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL);
this.compositeBar.shutdown();
super.shutdown();
}
......@@ -355,7 +355,7 @@ class PlaceHolderViewletActivityAction extends ViewletActivityAction {
super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, partService, telemetryService);
// Generate Placeholder CSS to show the icon in the activity bar
const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${this.class}`;
createCSSRule(iconClass, `-webkit-mask: url('${iconUrl}') no-repeat 50% 50%`);
createCSSRule(iconClass, `-webkit-mask: url('${iconUrl || ''}') no-repeat 50% 50%`);
this.enabled = false;
}
......
......@@ -63,6 +63,10 @@ export class CompositeBar extends Widget implements ICompositeBar {
this.compositeSizeInBar = new Map<string, number>();
}
public getCompositesFromStorage(): string[] {
return this.storedState.map(s => s.id);
}
public create(parent: HTMLElement): HTMLElement {
const actionBarDiv = parent.appendChild($('.composite-bar'));
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
......@@ -192,8 +196,6 @@ export class CompositeBar extends Widget implements ICompositeBar {
public pin(compositeId: string, open?: boolean): void {
if (this.model.setPinned(compositeId, true)) {
this.updateCompositeSwitcher();
// Persist
this.saveCompositeItems();
if (open) {
this.options.openComposite(compositeId)
......@@ -206,8 +208,6 @@ export class CompositeBar extends Widget implements ICompositeBar {
if (this.model.setPinned(compositeId, false)) {
this.updateCompositeSwitcher();
// Persist
this.saveCompositeItems();
const defaultCompositeId = this.options.getDefaultCompositeId();
......@@ -250,11 +250,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
public move(compositeId: string, toCompositeId: string): void {
if (this.model.move(compositeId, toCompositeId)) {
// timeout helps to prevent artifacts from showing up
setTimeout(() => {
this.updateCompositeSwitcher();
// Persist
this.saveCompositeItems();
}, 0);
setTimeout(() => this.updateCompositeSwitcher(), 0);
}
}
......@@ -395,6 +391,9 @@ export class CompositeBar extends Widget implements ICompositeBar {
this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });
}
// Persist
this.saveCompositeItems();
}
private getOverflowingComposites(): { id: string, name: string }[] {
......@@ -448,10 +447,6 @@ export class CompositeBar extends Widget implements ICompositeBar {
this.storedState = this.model.toJSON();
this.storageService.store(this.options.storageId, JSON.stringify(this.storedState), StorageScope.GLOBAL);
}
public shutdown(): void {
this.saveCompositeItems();
}
}
interface ISerializedCompositeBarItem {
......
......@@ -1074,7 +1074,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
this.centeredEditorLeftMarginRatio = 0.5;
// Restore centered layout position and size
const centeredLayoutDataString = this.storageService.get(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL);
const centeredLayoutDataString = this.storageService.get(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.WORKSPACE);
if (centeredLayoutDataString) {
const centeredLayout = <CenteredEditorLayoutData>JSON.parse(centeredLayoutDataString);
this.centeredEditorLeftMarginRatio = centeredLayout.leftMarginRatio;
......@@ -1985,7 +1985,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
leftMarginRatio: this.centeredEditorLeftMarginRatio,
size: this.centeredEditorSize
};
this.storageService.store(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, JSON.stringify(data), StorageScope.GLOBAL);
this.storageService.store(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, JSON.stringify(data), StorageScope.WORKSPACE);
}
public getVerticalSashTop(sash: Sash): number {
......@@ -2230,7 +2230,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
if (layout) {
this.layoutContainers();
}
this.storageService.remove(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL);
this.storageService.remove(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.WORKSPACE);
}
public getInstantiationService(position: Position): IInstantiationService {
......
......@@ -237,11 +237,6 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
return sizes;
}
public shutdown(): void {
this.compositeBar.shutdown();
super.shutdown();
}
private layoutCompositeBar(): void {
if (this.dimension) {
let availableWidth = this.dimension.width - 40; // take padding into account
......
......@@ -65,35 +65,35 @@
height: 2px;
}
.quick-input-checkbox-list {
.quick-input-list {
line-height: 22px;
}
.quick-input-checkbox-list .monaco-list {
.quick-input-list .monaco-list {
overflow: hidden;
max-height: calc(20 * 22px);
}
.quick-input-checkbox-list .quick-input-checkbox-list-entry {
.quick-input-list .quick-input-list-entry {
overflow: hidden;
display: flex;
height: 100%;
padding: 0 6px;
}
.quick-input-checkbox-list .quick-input-checkbox-list-label {
.quick-input-list .quick-input-list-label {
overflow: hidden;
display: flex;
height: 100%;
flex: 1;
}
.quick-input-checkbox-list .quick-input-checkbox-list-checkbox {
.quick-input-list .quick-input-list-checkbox {
align-self: center;
margin: 0;
}
.quick-input-checkbox-list .quick-input-checkbox-list-rows {
.quick-input-list .quick-input-list-rows {
overflow: hidden;
text-overflow: ellipsis;
display: flex;
......@@ -103,20 +103,20 @@
margin-left: 5px;
}
.quick-input-widget[data-type=pickMany] .quick-input-checkbox-list .quick-input-checkbox-list-rows {
.quick-input-widget[data-type=pickMany] .quick-input-list .quick-input-list-rows {
margin-left: 10px;
}
.quick-input-checkbox-list .quick-input-checkbox-list-rows > .quick-input-checkbox-list-row {
.quick-input-list .quick-input-list-rows > .quick-input-list-row {
display: flex;
align-items: center;
}
.quick-input-checkbox-list .quick-input-checkbox-list-rows .monaco-highlighted-label span {
.quick-input-list .quick-input-list-rows .monaco-highlighted-label span {
opacity: 1;
}
.quick-input-checkbox-list .quick-input-checkbox-list-label-meta {
.quick-input-list .quick-input-list-label-meta {
opacity: 0.7;
line-height: normal;
}
......@@ -7,17 +7,17 @@
import 'vs/css!./quickInput';
import { Component } from 'vs/workbench/common/component';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IQuickInputService, IPickOpenEntry, IPickOptions, IInputOptions, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import * as dom from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme';
import { IQuickOpenService, IPickOpenEntry, IPickOptions, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { TPromise } from 'vs/base/common/winjs.base';
import { CancellationToken } from 'vs/base/common/cancellation';
import { QuickInputCheckboxList } from './quickInputCheckboxList';
import { QuickInputList } from './quickInputList';
import { QuickInputBox } from './quickInputBox';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
......@@ -73,11 +73,12 @@ export interface TextInputParameters extends BaseInputParameters {
}
interface QuickInputUI {
container: HTMLElement;
checkAll: HTMLInputElement;
inputBox: QuickInputBox;
count: CountBadge;
message: HTMLElement;
checkboxList: QuickInputCheckboxList;
list: QuickInputList;
close: (ok?: true | Thenable<never>) => void;
}
......@@ -89,53 +90,54 @@ interface InputController<R> {
}
class PickOneController<T extends IPickOpenEntry> implements InputController<T> {
public showUI = { inputBox: true, checkboxList: true };
public showUI = { inputBox: true, list: true };
public result: TPromise<T>;
public ready: TPromise<void>;
public resolve: (ok?: true | Thenable<never>) => void;
public progress: (value: T) => void;
private closed = false;
private quickNavigate = false;
private disposables: IDisposable[] = [];
constructor(ui: QuickInputUI, parameters: PickOneParameters<T>) {
constructor(private ui: QuickInputUI, parameters: PickOneParameters<T>) {
this.result = new TPromise<T>((resolve, reject, progress) => {
this.resolve = ok => resolve(ok === true ? <T>ui.checkboxList.getFocusedElements()[0] : ok);
this.resolve = ok => resolve(ok === true ? <T>ui.list.getFocusedElements()[0] : ok);
this.progress = progress;
});
this.result.then(() => this.dispose());
ui.inputBox.value = '';
ui.inputBox.setPlaceholder(parameters.placeHolder || '');
ui.checkboxList.matchOnDescription = parameters.matchOnDescription;
ui.checkboxList.matchOnDetail = parameters.matchOnDetail;
ui.checkboxList.setElements([]);
ui.list.matchOnDescription = parameters.matchOnDescription;
ui.list.matchOnDetail = parameters.matchOnDetail;
ui.list.setElements([]);
this.ready = parameters.picks.then(elements => {
if (this.closed) {
return;
}
ui.checkboxList.setElements(elements);
ui.checkboxList.filter(ui.inputBox.value);
ui.checkboxList.focus('First');
ui.list.setElements(elements);
ui.list.filter(ui.inputBox.value);
ui.list.focus('First');
this.disposables.push(
ui.checkboxList.onSelectionChange(elements => {
ui.list.onSelectionChange(elements => {
if (elements[0]) {
ui.close(true);
}
}),
ui.inputBox.onDidChange(value => {
ui.checkboxList.filter(value);
ui.checkboxList.focus('First');
ui.list.filter(value);
ui.list.focus('First');
}),
ui.inputBox.onKeyDown(event => {
switch (event.keyCode) {
case KeyCode.DownArrow:
ui.checkboxList.focus('Next');
ui.list.focus('Next');
break;
case KeyCode.UpArrow:
ui.checkboxList.focus('Previous');
ui.list.focus('Previous');
break;
}
})
......@@ -143,6 +145,53 @@ class PickOneController<T extends IPickOpenEntry> implements InputController<T>
});
}
configureQuickNavigate(quickNavigate: IQuickNavigateConfiguration) {
if (this.quickNavigate) {
return;
}
this.quickNavigate = true;
this.disposables.push(dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e as KeyboardEvent);
const keyCode = keyboardEvent.keyCode;
// Select element when keys are pressed that signal it
const quickNavKeys = quickNavigate.keybindings;
const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => {
const [firstPart, chordPart] = k.getParts();
if (chordPart) {
return false;
}
if (firstPart.shiftKey && keyCode === KeyCode.Shift) {
if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) {
return false; // this is an optimistic check for the shift key being used to navigate back in quick open
}
return true;
}
if (firstPart.altKey && keyCode === KeyCode.Alt) {
return true;
}
if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) {
return true;
}
if (firstPart.metaKey && keyCode === KeyCode.Meta) {
return true;
}
return false;
});
if (wasTriggerKeyPressed) {
this.ui.close(true);
}
}));
}
private dispose() {
this.closed = true;
this.disposables = dispose(this.disposables);
......@@ -150,7 +199,7 @@ class PickOneController<T extends IPickOpenEntry> implements InputController<T>
}
class PickManyController<T extends IPickOpenEntry> implements InputController<T[]> {
public showUI = { checkAll: true, inputBox: true, count: true, ok: true, checkboxList: true };
public showUI = { checkAll: true, inputBox: true, count: true, ok: true, list: true };
public result: TPromise<T[]>;
public ready: TPromise<void>;
public resolve: (ok?: true | Thenable<never>) => void;
......@@ -160,42 +209,42 @@ class PickManyController<T extends IPickOpenEntry> implements InputController<T[
constructor(ui: QuickInputUI, parameters: PickManyParameters<T>) {
this.result = new TPromise<T[]>((resolve, reject, progress) => {
this.resolve = ok => resolve(ok === true ? <T[]>ui.checkboxList.getCheckedElements() : ok);
this.resolve = ok => resolve(ok === true ? <T[]>ui.list.getCheckedElements() : ok);
this.progress = progress;
});
this.result.then(() => this.dispose());
ui.inputBox.value = '';
ui.inputBox.setPlaceholder(parameters.placeHolder || '');
ui.checkboxList.matchOnDescription = parameters.matchOnDescription;
ui.checkboxList.matchOnDetail = parameters.matchOnDetail;
ui.checkboxList.setElements([]);
ui.checkAll.checked = ui.checkboxList.getAllVisibleChecked();
ui.count.setCount(ui.checkboxList.getCheckedCount());
ui.list.matchOnDescription = parameters.matchOnDescription;
ui.list.matchOnDetail = parameters.matchOnDetail;
ui.list.setElements([]);
ui.checkAll.checked = ui.list.getAllVisibleChecked();
ui.count.setCount(ui.list.getCheckedCount());
this.ready = parameters.picks.then(elements => {
if (this.closed) {
return;
}
ui.checkboxList.setElements(elements, true);
ui.checkboxList.filter(ui.inputBox.value);
ui.checkAll.checked = ui.checkboxList.getAllVisibleChecked();
ui.count.setCount(ui.checkboxList.getCheckedCount());
ui.list.setElements(elements, true);
ui.list.filter(ui.inputBox.value);
ui.checkAll.checked = ui.list.getAllVisibleChecked();
ui.count.setCount(ui.list.getCheckedCount());
this.disposables.push(
ui.inputBox.onDidChange(value => {
ui.checkboxList.filter(value);
ui.list.filter(value);
}),
ui.inputBox.onKeyDown(event => {
switch (event.keyCode) {
case KeyCode.DownArrow:
ui.checkboxList.focus('First');
ui.checkboxList.domFocus();
ui.list.focus('First');
ui.list.domFocus();
break;
case KeyCode.UpArrow:
ui.checkboxList.focus('Last');
ui.checkboxList.domFocus();
ui.list.focus('Last');
ui.list.domFocus();
break;
}
})
......@@ -301,7 +350,6 @@ export class QuickInputService extends Component implements IQuickInputService {
private static readonly MAX_WIDTH = 600; // Max total width of quick open widget
private layoutDimensions: dom.Dimension;
private container: HTMLElement;
private filterContainer: HTMLElement;
private countContainer: HTMLElement;
private okContainer: HTMLElement;
......@@ -348,22 +396,22 @@ export class QuickInputService extends Component implements IQuickInputService {
}
private create() {
if (this.container) {
if (this.ui) {
return;
}
const workbench = document.getElementById(this.partService.getWorkbenchElementId());
this.container = dom.append(workbench, $('.quick-input-widget'));
this.container.tabIndex = -1;
this.container.style.display = 'none';
const container = dom.append(workbench, $('.quick-input-widget'));
container.tabIndex = -1;
container.style.display = 'none';
const headerContainer = dom.append(this.container, $('.quick-input-header'));
const headerContainer = dom.append(container, $('.quick-input-header'));
const checkAll = <HTMLInputElement>dom.append(headerContainer, $('input.quick-input-check-all'));
checkAll.type = 'checkbox';
this.toUnbind.push(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => {
const checked = checkAll.checked;
checkboxList.setAllVisibleChecked(checked);
list.setAllVisibleChecked(checked);
}));
this.toUnbind.push(dom.addDisposableListener(checkAll, dom.EventType.CLICK, e => {
if (e.x || e.y) { // Avoid 'click' triggered by 'space'...
......@@ -390,29 +438,29 @@ export class QuickInputService extends Component implements IQuickInputService {
}
}));
const message = dom.append(this.container, $('.quick-input-message'));
const message = dom.append(container, $('.quick-input-message'));
this.progressBar = new ProgressBar(this.container);
this.progressBar = new ProgressBar(container);
dom.addClass(this.progressBar.getContainer(), 'quick-input-progress');
this.toUnbind.push(attachProgressBarStyler(this.progressBar, this.themeService));
const checkboxList = this.instantiationService.createInstance(QuickInputCheckboxList, this.container);
this.toUnbind.push(checkboxList);
this.toUnbind.push(checkboxList.onAllVisibleCheckedChanged(checked => {
const list = this.instantiationService.createInstance(QuickInputList, container);
this.toUnbind.push(list);
this.toUnbind.push(list.onAllVisibleCheckedChanged(checked => {
checkAll.checked = checked;
}));
this.toUnbind.push(checkboxList.onCheckedCountChanged(c => {
this.toUnbind.push(list.onCheckedCountChanged(c => {
count.setCount(c);
}));
this.toUnbind.push(checkboxList.onLeave(() => {
this.toUnbind.push(list.onLeave(() => {
// Defer to avoid the input field reacting to the triggering key.
setTimeout(() => {
inputBox.setFocus();
checkboxList.clearFocus();
list.clearFocus();
}, 0);
}));
this.toUnbind.push(
chain(checkboxList.onFocusChange)
chain(list.onFocusChange)
.map(e => e[0])
.filter(e => !!e)
.latch()
......@@ -424,13 +472,13 @@ export class QuickInputService extends Component implements IQuickInputService {
})
);
this.toUnbind.push(dom.addDisposableListener(this.container, 'focusout', (e: FocusEvent) => {
if (e.relatedTarget === this.container) {
this.toUnbind.push(dom.addDisposableListener(container, 'focusout', (e: FocusEvent) => {
if (e.relatedTarget === container) {
(<HTMLElement>e.target).focus();
return;
}
for (let element = <Element>e.relatedTarget; element; element = element.parentElement) {
if (element === this.container) {
if (element === container) {
return;
}
}
......@@ -438,7 +486,7 @@ export class QuickInputService extends Component implements IQuickInputService {
this.close(undefined, true);
}
}));
this.toUnbind.push(dom.addDisposableListener(this.container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
this.toUnbind.push(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
switch (event.keyCode) {
case KeyCode.Enter:
......@@ -453,7 +501,7 @@ export class QuickInputService extends Component implements IQuickInputService {
break;
case KeyCode.Tab:
if (!event.altKey && !event.ctrlKey && !event.metaKey) {
const inputs = [].slice.call(this.container.querySelectorAll('input'))
const inputs = [].slice.call(container.querySelectorAll('input'))
.filter(input => input.style.display !== 'none');
if (event.shiftKey && event.target === inputs[0]) {
dom.EventHelper.stop(e, true);
......@@ -469,7 +517,7 @@ export class QuickInputService extends Component implements IQuickInputService {
this.toUnbind.push(this.quickOpenService.onShow(() => this.close()));
this.ui = { checkAll, inputBox, count, message, checkboxList, close: ok => this.close(ok) };
this.ui = { container, checkAll, inputBox, count, message, list, close: ok => this.close(ok) };
this.updateStyles();
}
......@@ -483,7 +531,7 @@ export class QuickInputService extends Component implements IQuickInputService {
const result = resolved
.then(() => {
this.inQuickOpen('quickInput', false);
this.container.style.display = 'none';
this.ui.container.style.display = 'none';
if (!focusLost) {
this.restoreFocus();
}
......@@ -493,7 +541,7 @@ export class QuickInputService extends Component implements IQuickInputService {
}
}
this.inQuickOpen('quickInput', false);
this.container.style.display = 'none';
this.ui.container.style.display = 'none';
if (!focusLost) {
this.restoreFocus();
}
......@@ -540,7 +588,7 @@ export class QuickInputService extends Component implements IQuickInputService {
this.controller.resolve();
}
this.container.setAttribute('data-type', parameters.type);
this.ui.container.setAttribute('data-type', parameters.type);
this.ignoreFocusLost = parameters.ignoreFocusLost;
......@@ -554,12 +602,12 @@ export class QuickInputService extends Component implements IQuickInputService {
this.countContainer.style.display = this.controller.showUI.count ? '' : 'none';
this.okContainer.style.display = this.controller.showUI.ok ? '' : 'none';
this.ui.message.style.display = this.controller.showUI.message ? '' : 'none';
this.ui.checkboxList.display(this.controller.showUI.checkboxList);
this.ui.list.display(this.controller.showUI.list);
if (this.container.style.display === 'none') {
if (this.ui.container.style.display === 'none') {
this.inQuickOpen('quickInput', true);
}
this.container.style.display = '';
this.ui.container.style.display = '';
this.updateLayout();
this.ui.inputBox.setFocus();
......@@ -604,13 +652,16 @@ export class QuickInputService extends Component implements IQuickInputService {
toggle() {
if (this.isDisplayed() && this.controller instanceof PickManyController) {
this.ui.checkboxList.toggleCheckbox();
this.ui.list.toggleCheckbox();
}
}
navigate(next: boolean) {
if (this.isDisplayed() && this.ui.checkboxList.isDisplayed()) {
this.ui.checkboxList.focus(next ? 'Next' : 'Previous');
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) {
if (this.isDisplayed() && this.ui.list.isDisplayed()) {
this.ui.list.focus(next ? 'Next' : 'Previous');
if (quickNavigate && this.controller instanceof PickOneController) {
this.controller.configureQuickNavigate(quickNavigate);
}
}
}
......@@ -628,17 +679,17 @@ export class QuickInputService extends Component implements IQuickInputService {
}
private updateLayout() {
if (this.layoutDimensions && this.container) {
if (this.layoutDimensions && this.ui) {
const titlebarOffset = this.partService.getTitleBarOffset();
this.container.style.top = `${titlebarOffset}px`;
this.ui.container.style.top = `${titlebarOffset}px`;
const style = this.container.style;
const style = this.ui.container.style;
const width = Math.min(this.layoutDimensions.width * 0.62 /* golden cut */, QuickInputService.MAX_WIDTH);
style.width = width + 'px';
style.marginLeft = '-' + (width / 2) + 'px';
this.ui.inputBox.layout();
this.ui.checkboxList.layout();
this.ui.list.layout();
}
}
......@@ -646,22 +697,21 @@ export class QuickInputService extends Component implements IQuickInputService {
const theme = this.themeService.getTheme();
if (this.ui) {
this.ui.inputBox.style(theme);
this.ui.checkboxList.style(theme);
}
if (this.container) {
if (this.ui) {
const sideBarBackground = theme.getColor(SIDE_BAR_BACKGROUND);
this.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : undefined;
this.ui.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : undefined;
const sideBarForeground = theme.getColor(SIDE_BAR_FOREGROUND);
this.container.style.color = sideBarForeground ? sideBarForeground.toString() : undefined;
this.ui.container.style.color = sideBarForeground ? sideBarForeground.toString() : undefined;
const contrastBorderColor = theme.getColor(contrastBorder);
this.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : undefined;
this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : undefined;
const widgetShadowColor = theme.getColor(widgetShadow);
this.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : undefined;
this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : undefined;
}
}
private isDisplayed() {
return this.container && this.container.style.display !== 'none';
return this.ui && this.ui.container.style.display !== 'none';
}
}
......
......@@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IPickOpenEntry } from 'vs/platform/quickinput/common/quickInput';
import { IMatch } from 'vs/base/common/filters';
import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon';
import { compareAnything } from 'vs/base/common/comparers';
......@@ -25,24 +25,24 @@ import { memoize } from 'vs/base/common/decorators';
import { range } from 'vs/base/common/arrays';
import * as platform from 'vs/base/common/platform';
import { listFocusBackground } from 'vs/platform/theme/common/colorRegistry';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
const $ = dom.$;
interface ICheckableElement {
interface IListElement {
index: number;
item: IPickOpenEntry;
checked: boolean;
checked?: boolean;
}
class CheckableElement implements ICheckableElement {
class ListElement implements IListElement {
index: number;
item: IPickOpenEntry;
shouldAlwaysShow = false;
hidden = false;
private _onChecked = new Emitter<boolean>();
onChecked = this._onChecked.event;
_checked: boolean;
_checked?: boolean;
get checked() {
return this._checked;
}
......@@ -56,59 +56,59 @@ class CheckableElement implements ICheckableElement {
descriptionHighlights?: IMatch[];
detailHighlights?: IMatch[];
constructor(init: ICheckableElement) {
constructor(init: IListElement) {
assign(this, init);
}
}
interface ICheckableElementTemplateData {
interface IListElementTemplateData {
checkbox: HTMLInputElement;
label: IconLabel;
detail: HighlightedLabel;
element: CheckableElement;
element: ListElement;
toDisposeElement: IDisposable[];
toDisposeTemplate: IDisposable[];
}
class CheckableElementRenderer implements IRenderer<CheckableElement, ICheckableElementTemplateData> {
class ListElementRenderer implements IRenderer<ListElement, IListElementTemplateData> {
static readonly ID = 'checkableelement';
static readonly ID = 'listelement';
get templateId() {
return CheckableElementRenderer.ID;
return ListElementRenderer.ID;
}
renderTemplate(container: HTMLElement): ICheckableElementTemplateData {
const data: ICheckableElementTemplateData = Object.create(null);
renderTemplate(container: HTMLElement): IListElementTemplateData {
const data: IListElementTemplateData = Object.create(null);
data.toDisposeElement = [];
data.toDisposeTemplate = [];
const entry = dom.append(container, $('.quick-input-checkbox-list-entry'));
const entry = dom.append(container, $('.quick-input-list-entry'));
// Checkbox
const label = dom.append(entry, $('label.quick-input-checkbox-list-label'));
data.checkbox = <HTMLInputElement>dom.append(label, $('input.quick-input-checkbox-list-checkbox'));
const label = dom.append(entry, $('label.quick-input-list-label'));
data.checkbox = <HTMLInputElement>dom.append(label, $('input.quick-input-list-checkbox'));
data.checkbox.type = 'checkbox';
data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => {
data.element.checked = data.checkbox.checked;
}));
// Rows
const rows = dom.append(label, $('.quick-input-checkbox-list-rows'));
const row1 = dom.append(rows, $('.quick-input-checkbox-list-row'));
const row2 = dom.append(rows, $('.quick-input-checkbox-list-row'));
const rows = dom.append(label, $('.quick-input-list-rows'));
const row1 = dom.append(rows, $('.quick-input-list-row'));
const row2 = dom.append(rows, $('.quick-input-list-row'));
// Label
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true });
// Detail
const detailContainer = dom.append(row2, $('.quick-input-checkbox-list-label-meta'));
const detailContainer = dom.append(row2, $('.quick-input-list-label-meta'));
data.detail = new HighlightedLabel(detailContainer);
return data;
}
renderElement(element: CheckableElement, index: number, data: ICheckableElementTemplateData): void {
renderElement(element: ListElement, index: number, data: IListElementTemplateData): void {
data.toDisposeElement = dispose(data.toDisposeElement);
data.element = element;
if (element.checked === undefined) {
......@@ -116,8 +116,8 @@ class CheckableElementRenderer implements IRenderer<CheckableElement, ICheckable
} else {
data.checkbox.style.display = '';
data.checkbox.checked = element.checked;
}
data.toDisposeElement.push(element.onChecked(checked => data.checkbox.checked = checked));
}
const { labelHighlights, descriptionHighlights, detailHighlights } = element;
......@@ -132,28 +132,28 @@ class CheckableElementRenderer implements IRenderer<CheckableElement, ICheckable
data.detail.set(element.item.detail, detailHighlights);
}
disposeTemplate(data: ICheckableElementTemplateData): void {
disposeTemplate(data: IListElementTemplateData): void {
data.toDisposeElement = dispose(data.toDisposeElement);
data.toDisposeTemplate = dispose(data.toDisposeTemplate);
}
}
class CheckableElementDelegate implements IDelegate<CheckableElement> {
class ListElementDelegate implements IDelegate<ListElement> {
getHeight(element: CheckableElement): number {
getHeight(element: ListElement): number {
return element.item.detail ? 44 : 22;
}
getTemplateId(element: CheckableElement): string {
return CheckableElementRenderer.ID;
getTemplateId(element: ListElement): string {
return ListElementRenderer.ID;
}
}
export class QuickInputCheckboxList {
export class QuickInputList {
private container: HTMLElement;
private list: WorkbenchList<CheckableElement>;
private elements: CheckableElement[] = [];
private list: WorkbenchList<ListElement>;
private elements: ListElement[] = [];
matchOnDescription = false;
matchOnDetail = false;
private _onAllVisibleCheckedChanged = new Emitter<boolean>(); // TODO: Debounce
......@@ -170,12 +170,12 @@ export class QuickInputCheckboxList {
private parent: HTMLElement,
@IInstantiationService private instantiationService: IInstantiationService
) {
this.container = dom.append(this.parent, $('.quick-input-checkbox-list'));
const delegate = new CheckableElementDelegate();
this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new CheckableElementRenderer()], {
this.container = dom.append(this.parent, $('.quick-input-list'));
const delegate = new ListElementDelegate();
this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new ListElementRenderer()], {
identityProvider: element => element.label,
multipleSelectionSupport: false
}) as WorkbenchList<CheckableElement>;
}) as WorkbenchList<ListElement>;
this.disposables.push(this.list);
this.disposables.push(this.list.onKeyDown(e => {
const event = new StandardKeyboardEvent(e);
......@@ -228,7 +228,7 @@ export class QuickInputCheckboxList {
return this.allVisibleChecked(this.elements, false);
}
private allVisibleChecked(elements: CheckableElement[], whenNoneVisible = true) {
private allVisibleChecked(elements: ListElement[], whenNoneVisible = true) {
for (let i = 0, n = elements.length; i < n; i++) {
const element = elements[i];
if (!element.hidden) {
......@@ -269,12 +269,14 @@ export class QuickInputCheckboxList {
setElements(elements: IPickOpenEntry[], canCheck = false): void {
this.elementDisposables = dispose(this.elementDisposables);
this.elements = elements.map((item, index) => new CheckableElement({
this.elements = elements.map((item, index) => new ListElement({
index,
item,
checked: canCheck ? !!item.picked : undefined
}));
if (canCheck) {
this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents())));
}
this.list.splice(0, this.list.length, this.elements);
this.list.setFocus([]);
}
......@@ -381,12 +383,6 @@ export class QuickInputCheckboxList {
}
}
style(theme: ITheme) {
this.list.style({
listInactiveFocusBackground: theme.getColor(listFocusBackground),
});
}
display(display: boolean) {
this.container.style.display = display ? '' : 'none';
}
......@@ -408,7 +404,7 @@ export class QuickInputCheckboxList {
}
}
function compareEntries(elementA: CheckableElement, elementB: CheckableElement, lookFor: string): number {
function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number {
const labelHighlightsA = elementA.labelHighlights || [];
const labelHighlightsB = elementB.labelHighlights || [];
......@@ -422,3 +418,12 @@ function compareEntries(elementA: CheckableElement, elementB: CheckableElement,
return compareAnything(elementA.item.label, elementB.item.label, lookFor);
}
registerThemingParticipant((theme, collector) => {
// Override inactive focus background with active focus background for single-pick case.
const listInactiveFocusBackground = theme.getColor(listFocusBackground);
if (listInactiveFocusBackground) {
collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { background-color: ${listInactiveFocusBackground}; }`);
collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused:hover { background-color: ${listInactiveFocusBackground}; }`);
}
});
......@@ -58,7 +58,7 @@ export class BaseQuickOpenNavigateAction extends Action {
const quickNavigate = this.quickNavigate ? { keybindings: keys } : void 0;
this.quickOpenService.navigate(this.next, quickNavigate);
this.quickInputService.navigate(this.next);
this.quickInputService.navigate(this.next, quickNavigate);
return TPromise.as(true);
}
......@@ -74,7 +74,7 @@ export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHan
const quickNavigate = { keybindings: keys };
quickOpenService.navigate(next, quickNavigate);
quickInputService.navigate(next);
quickInputService.navigate(next, quickNavigate);
};
}
......
......@@ -677,7 +677,7 @@ export class Workbench extends Disposable implements IPartService {
}
// Restore Forced Editor Center Mode
if (this.storageService.getBoolean(Workbench.centeredEditorLayoutActiveStorageKey, StorageScope.GLOBAL, false)) {
if (this.storageService.getBoolean(Workbench.centeredEditorLayoutActiveStorageKey, StorageScope.WORKSPACE, false)) {
this.centeredEditorLayoutActive = true;
}
......@@ -1244,7 +1244,7 @@ export class Workbench extends Disposable implements IPartService {
// - IEditorInput.supportsCenteredEditorLayout() no longer supported
centerEditorLayout(active: boolean, skipLayout?: boolean): void {
this.centeredEditorLayoutActive = active;
this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, this.centeredEditorLayoutActive, StorageScope.GLOBAL);
this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, this.centeredEditorLayoutActive, StorageScope.WORKSPACE);
// Enter Centered Editor Layout
if (active) {
......
......@@ -66,6 +66,9 @@ export function renderExpressionValue(expressionOrValue: IExpression | string, c
if (value !== Expression.DEFAULT_VALUE) {
dom.addClass(container, 'error');
}
} else if (options.showChanged && (<any>expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) {
// value changed color has priority over other colors.
container.className = 'value changed';
}
if (options.colorize && typeof expressionOrValue !== 'string') {
......@@ -80,11 +83,6 @@ export function renderExpressionValue(expressionOrValue: IExpression | string, c
}
}
if (options.showChanged && (<any>expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) {
// value changed color has priority over other colors.
container.className = 'value changed';
}
if (options.maxValueLength && value.length > options.maxValueLength) {
value = value.substr(0, options.maxValueLength) + '...';
}
......@@ -106,7 +104,7 @@ export function renderVariable(variable: Variable, data: IVariableTemplateData,
}
if (variable.value) {
data.name.textContent += variable.name ? ':' : '';
data.name.textContent += (typeof variable.name === 'string') ? ':' : '';
renderExpressionValue(variable, data.value, {
showChanged,
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
......
......@@ -799,18 +799,17 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
const extension: Extension = installingExtension ? installingExtension : zipPath ? new Extension(this.galleryService, this.stateProvider, null, null, this.telemetryService) : null;
if (extension) {
this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;
const installed = this.installed.filter(e => e.id === extension.id)[0];
if (!error) {
extension.local = local;
const installed = this.installed.filter(e => e.id === extension.id)[0];
if (installed) {
installed.local = local;
} else {
this.installed.push(extension);
}
}
if (extension.gallery) {
// Report telemetry only for gallery extensions
if (extension.gallery && !installed) {
// Report recommendation telemetry only for gallery extensions that are first time installs
this.reportExtensionRecommendationsTelemetry(installingExtension);
}
}
......
......@@ -126,7 +126,7 @@ export class ExplorerItem {
}
public get isRoot(): boolean {
return this.resource.toString() === this.root.resource.toString();
return this === this.root;
}
public static create(raw: IFileStat, root: ExplorerItem, resolveTo?: URI[]): ExplorerItem {
......
......@@ -1492,7 +1492,6 @@ export function getWellFormedFileName(filename: string): string {
// Remove trailing dots, slashes, and spaces
filename = strings.rtrim(filename, '.');
filename = strings.rtrim(filename, ' ');
filename = strings.rtrim(filename, '/');
filename = strings.rtrim(filename, '\\');
......
......@@ -23,11 +23,11 @@ export interface IResourceCreator {
export class OutputLinkComputer {
private ctx: IWorkerContext;
private patterns: Map<string /* folder fsPath */, RegExp[]>;
private patterns: Map<URI /* folder uri */, RegExp[]>;
constructor(ctx: IWorkerContext, createData: ICreateData) {
this.ctx = ctx;
this.patterns = new Map<string, RegExp[]>();
this.patterns = new Map<URI, RegExp[]>();
this.computePatterns(createData);
}
......@@ -40,7 +40,7 @@ export class OutputLinkComputer {
const workspaceFolders = createData.workspaceFolders.map(r => URI.parse(r));
workspaceFolders.forEach(workspaceFolder => {
const patterns = OutputLinkComputer.createPatterns(workspaceFolder);
this.patterns.set(workspaceFolder.fsPath, patterns);
this.patterns.set(workspaceFolder, patterns);
});
}
......@@ -66,11 +66,11 @@ export class OutputLinkComputer {
const lines = model.getValue().split(/\r\n|\r|\n/);
// For each workspace root patterns
this.patterns.forEach((folderPatterns, folderPath) => {
this.patterns.forEach((folderPatterns, folderUri) => {
const resourceCreator: IResourceCreator = {
toResource: (folderRelativePath: string): URI => {
if (typeof folderRelativePath === 'string') {
return URI.file(paths.join(folderPath, folderRelativePath));
return folderUri.with({ path: paths.join(folderUri.path, folderRelativePath) });
}
return null;
......@@ -88,9 +88,10 @@ export class OutputLinkComputer {
public static createPatterns(workspaceFolder: URI): RegExp[] {
const patterns: RegExp[] = [];
const workspaceFolderPath = workspaceFolder.scheme === 'file' ? workspaceFolder.fsPath : workspaceFolder.path;
const workspaceFolderVariants = arrays.distinct([
paths.normalize(workspaceFolder.fsPath, true),
paths.normalize(workspaceFolder.fsPath, false)
paths.normalize(workspaceFolderPath, true),
paths.normalize(workspaceFolderPath, false)
]);
workspaceFolderVariants.forEach(workspaceFolderVariant => {
......@@ -181,7 +182,3 @@ export class OutputLinkComputer {
return links;
}
}
export function create(ctx: IWorkerContext, createData: ICreateData): OutputLinkComputer {
return new OutputLinkComputer(ctx, createData);
}
......@@ -237,7 +237,7 @@
- Make up for that space with a negative margin on the settings-body
This is risky, consider a different approach
*/
*/
.settings-editor .settings-list-container .monaco-list-row {
overflow: visible;
}
......@@ -251,6 +251,38 @@
margin-left: -15px;
}
/* .settings-editor .settings-list-container .monaco-scrollable-element > .shadow.top {
width: calc(100% - 3px);
} */
\ No newline at end of file
.settings-editor > .settings-body > .settings-list-container .settings-group-title-expanded,
.settings-editor > .settings-body > .settings-list-container .settings-group-title-collapsed {
cursor: pointer;
}
.vs-dark .settings-editor > .settings-body > .settings-list-container .settings-group-title-collapsed::before {
background-image: url(collapsed-dark.svg);
}
.settings-editor > .settings-body > .settings-list-container .settings-group-title-collapsed::before {
display: inline-block;
background-image: url(collapsed.svg);
}
.vs-dark .settings-editor > .settings-body > .settings-list-container .settings-group-title-expanded::before {
background-image: url(expanded-dark.svg);
}
.settings-editor > .settings-body > .settings-list-container .settings-group-title-expanded::before {
display: inline-block;
background-image: url(expanded.svg);
}
.settings-editor > .settings-body > .settings-list-container .settings-group-title-collapsed::before,
.settings-editor > .settings-body > .settings-list-container .settings-group-title-expanded::before {
background-size: 16px;
background-position: 50% 50%;
background-repeat: no-repeat;
width: 16px;
height: 22px;
position: absolute;
content: ' ';
left: -19px;
top: 14px;
}
\ No newline at end of file
......@@ -61,8 +61,15 @@ interface ISettingItemEntry extends IListEntry {
enum?: string[];
}
enum ExpandState {
Expanded,
Collapsed,
NA
}
interface IGroupTitleEntry extends IListEntry {
title: string;
expandState: ExpandState;
}
interface IButtonRowEntry extends IListEntry {
......@@ -113,6 +120,8 @@ export class SettingsEditor2 extends BaseEditor {
private searchResultModel: SearchResultModel;
private pendingSettingModifiedReport: { key: string, value: any };
private groupExpanded = new Map<string, boolean>();
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService private configurationService: IConfigurationService,
......@@ -250,11 +259,18 @@ export class SettingsEditor2 extends BaseEditor {
const buttonItemRenderer = new ButtonRowRenderer();
this._register(buttonItemRenderer.onDidClick(e => this.onShowAllSettingsClicked()));
const groupTitleRenderer = new GroupTitleRenderer();
this._register(groupTitleRenderer.onDidClickGroup(e => {
const isExpanded = !!this.groupExpanded.get(e);
this.groupExpanded.set(e, !isExpanded);
this.renderEntries();
}));
this.settingsList = this._register(this.instantiationService.createInstance(
WorkbenchList,
this.settingsListContainer,
new SettingItemDelegate(),
[settingItemRenderer, new GroupTitleRenderer(), buttonItemRenderer],
[settingItemRenderer, groupTitleRenderer, buttonItemRenderer],
{
identityProvider: e => e.id,
ariaLabel: localize('settingsListLabel', "Settings"),
......@@ -530,7 +546,10 @@ export class SettingsEditor2 extends BaseEditor {
}
const group = this.defaultSettingsEditorModel.settingsGroups[groupIdx];
const isExpanded = groupIdx === 0 || this.groupExpanded.get(group.id);
const groupEntries = [];
if (isExpanded) {
for (const section of group.sections) {
for (const setting of section.settings) {
const entry = this.settingToEntry(setting);
......@@ -539,12 +558,18 @@ export class SettingsEditor2 extends BaseEditor {
}
}
}
}
if (!isExpanded || groupEntries.length) {
const expandState = groupIdx === 0 ? ExpandState.NA :
isExpanded ? ExpandState.Expanded :
ExpandState.Collapsed;
if (groupEntries.length) {
entries.push(<IGroupTitleEntry>{
id: group.id,
templateId: SETTINGS_GROUP_ENTRY_TEMPLATE_ID,
title: group.title
title: group.title,
expandState
});
entries.push(...groupEntries);
......@@ -623,9 +648,12 @@ class SettingItemDelegate implements IDelegate<IListEntry> {
}
}
interface ISettingItemTemplate {
parent: HTMLElement;
interface IDisposableTemplate {
toDispose: IDisposable[];
}
interface ISettingItemTemplate extends IDisposableTemplate {
parent: HTMLElement;
containerElement: HTMLElement;
categoryElement: HTMLElement;
......@@ -635,14 +663,14 @@ interface ISettingItemTemplate {
overridesElement: HTMLElement;
}
interface IGroupTitleTemplate {
interface IGroupTitleTemplate extends IDisposableTemplate {
context?: IGroupTitleEntry;
parent: HTMLElement;
labelElement: HTMLElement;
}
interface IButtonRowTemplate {
interface IButtonRowTemplate extends IDisposableTemplate {
parent: HTMLElement;
toDispose: IDisposable[];
button: Button;
entry?: IButtonRowEntry;
......@@ -701,18 +729,25 @@ class SettingItemRenderer implements IRenderer<ISettingItemEntry, ISettingItemTe
renderElement(entry: ISettingItemEntry, index: number, template: ISettingItemTemplate): void {
DOM.toggleClass(template.parent, 'odd', index % 2 === 1);
let titleTooltip = entry.key;
if (entry.isConfigured) {
titleTooltip += ' - ' + localize('configuredTitleToolip', "This setting is configured");
}
const settingKeyDisplay = settingKeyToDisplayFormat(entry.key);
template.categoryElement.textContent = settingKeyDisplay.category + ': ';
template.categoryElement.title = entry.key;
template.categoryElement.title = titleTooltip;
template.labelElement.textContent = settingKeyDisplay.label;
template.labelElement.title = entry.key;
template.labelElement.title = titleTooltip;
template.descriptionElement.textContent = entry.description;
template.descriptionElement.title = entry.description;
DOM.toggleClass(template.parent, 'is-configured', entry.isConfigured);
this.renderValue(entry, template);
const resetButton = new Button(template.valueElement);
resetButton.element.title = localize('resetButtonTitle', "Reset");
resetButton.element.classList.add('setting-reset-button');
attachButtonStyler(resetButton, this.themeService, {
buttonBackground: Color.transparent.toString(),
......@@ -800,23 +835,51 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
class GroupTitleRenderer implements IRenderer<IGroupTitleEntry, IGroupTitleTemplate> {
private static readonly EXPANDED_CLASS = 'settings-group-title-expanded';
private static readonly COLLAPSED_CLASS = 'settings-group-title-collapsed';
private readonly _onDidClickGroup: Emitter<string> = new Emitter<string>();
public readonly onDidClickGroup: Event<string> = this._onDidClickGroup.event;
get templateId(): string { return SETTINGS_GROUP_ENTRY_TEMPLATE_ID; }
renderTemplate(parent: HTMLElement): IGroupTitleTemplate {
DOM.addClass(parent, 'group-title');
const labelElement = DOM.append(parent, $('h2.group-title-label'));
return {
const labelElement = DOM.append(parent, $('h2.settings-group-title-label'));
const toDispose = [];
const template: IGroupTitleTemplate = {
parent: parent,
labelElement
labelElement,
toDispose
};
toDispose.push(DOM.addDisposableListener(labelElement, 'click', () => {
if (template.context) {
this._onDidClickGroup.fire(template.context.id);
}
}));
return template;
}
renderElement(entry: IGroupTitleEntry, index: number, template: IGroupTitleTemplate): void {
template.context = entry;
template.labelElement.textContent = entry.title;
template.labelElement.classList.remove(GroupTitleRenderer.EXPANDED_CLASS);
template.labelElement.classList.remove(GroupTitleRenderer.COLLAPSED_CLASS);
if (entry.expandState === ExpandState.Expanded) {
template.labelElement.classList.add(GroupTitleRenderer.EXPANDED_CLASS);
} else if (entry.expandState === ExpandState.Collapsed) {
template.labelElement.classList.add(GroupTitleRenderer.COLLAPSED_CLASS);
}
}
disposeTemplate(template: IGroupTitleTemplate): void {
dispose(template.toDispose);
}
}
......
......@@ -209,6 +209,47 @@ export class ShowPreviousSearchTermAction extends Action {
return TPromise.as(null);
}
}
export class ShowNextReplaceTermAction extends Action {
public static readonly ID = 'search.replaceHistory.showNext';
public static readonly LABEL = nls.localize('nextReplaceTerm', "Show Next Search Replace Term");
constructor(id: string, label: string,
@IViewletService private viewletService: IViewletService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IPanelService private panelService: IPanelService
) {
super(id, label);
this.enabled = this.contextKeyService.contextMatchesRules(Constants.SearchViewVisibleKey);
}
public run(): TPromise<any> {
const searchView = getSearchView(this.viewletService, this.panelService);
searchView.searchAndReplaceWidget.showNextReplaceTerm();
return TPromise.as(null);
}
}
export class ShowPreviousReplaceTermAction extends Action {
public static readonly ID = 'search.replaceHistory.showPrevious';
public static readonly LABEL = nls.localize('previousReplaceTerm', "Show Previous Search Replace Term");
constructor(id: string, label: string,
@IViewletService private viewletService: IViewletService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IPanelService private panelService: IPanelService
) {
super(id, label);
this.enabled = this.contextKeyService.contextMatchesRules(Constants.SearchViewVisibleKey);
}
public run(): TPromise<any> {
const searchView = getSearchView(this.viewletService, this.panelService);
searchView.searchAndReplaceWidget.showPreviousReplaceTerm();
return TPromise.as(null);
}
}
export class FocusNextInputAction extends Action {
......
......@@ -331,13 +331,15 @@ export class SearchView extends Viewlet implements IViewlet, IPanel {
let isWholeWords = this.viewletSettings['query.wholeWords'] === true;
let isCaseSensitive = this.viewletSettings['query.caseSensitive'] === true;
let searchHistory = this.viewletSettings['query.searchHistory'] || [];
let replaceHistory = this.viewletSettings['query.replaceHistory'] || [];
this.searchWidget = this.instantiationService.createInstance(SearchWidget, builder, <ISearchWidgetOptions>{
value: contentPattern,
isRegex: isRegex,
isCaseSensitive: isCaseSensitive,
isWholeWords: isWholeWords,
history: searchHistory,
searchHistory: searchHistory,
replaceHistory: replaceHistory,
historyLimit: SearchView.MAX_HISTORY_ITEMS
});
......@@ -1519,13 +1521,15 @@ export class SearchView extends Viewlet implements IViewlet, IPanel {
const patternExcludes = this.inputPatternExcludes.getValue().trim();
const patternIncludes = this.inputPatternIncludes.getValue().trim();
const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
const searchHistory = this.searchWidget.getHistory();
const searchHistory = this.searchWidget.getSearchHistory();
const replaceHistory = this.searchWidget.getReplaceHistory();
const patternExcludesHistory = this.inputPatternExcludes.getHistory();
const patternIncludesHistory = this.inputPatternIncludes.getHistory();
// store memento
this.viewletSettings['query.contentPattern'] = contentPattern;
this.viewletSettings['query.searchHistory'] = searchHistory;
this.viewletSettings['query.replaceHistory'] = replaceHistory;
this.viewletSettings['query.regex'] = isRegex;
this.viewletSettings['query.wholeWords'] = isWholeWords;
this.viewletSettings['query.caseSensitive'] = isCaseSensitive;
......
......@@ -38,8 +38,9 @@ export interface ISearchWidgetOptions {
isRegex?: boolean;
isCaseSensitive?: boolean;
isWholeWords?: boolean;
history?: string[];
searchHistory?: string[];
historyLimit?: number;
replaceHistory?: string[];
}
class ReplaceAllAction extends Action {
......@@ -96,6 +97,7 @@ export class SearchWidget extends Widget {
private replaceActionBar: ActionBar;
private searchHistory: HistoryNavigator<string>;
private replaceHistory: HistoryNavigator<string>;
private ignoreGlobalFindBufferOnNextFocus = false;
private previousGlobalFindBufferValue: string;
......@@ -128,7 +130,8 @@ export class SearchWidget extends Widget {
@IConfigurationService private configurationService: IConfigurationService
) {
super();
this.searchHistory = new HistoryNavigator<string>(options.history, options.historyLimit);
this.searchHistory = new HistoryNavigator<string>(options.searchHistory, options.historyLimit);
this.replaceHistory = new HistoryNavigator<string>(options.replaceHistory, options.historyLimit);
this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.keyBindingService);
this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.keyBindingService);
this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.keyBindingService);
......@@ -176,10 +179,14 @@ export class SearchWidget extends Widget {
}
}
public getHistory(): string[] {
public getSearchHistory(): string[] {
return this.searchHistory.getHistory();
}
public getReplaceHistory(): string[] {
return this.replaceHistory.getHistory();
}
public clearHistory(): void {
this.searchHistory.clear();
}
......@@ -204,6 +211,26 @@ export class SearchWidget extends Widget {
}
}
public showNextReplaceTerm() {
let next = this.replaceHistory.next();
if (next) {
this.replaceInput.value = next;
}
}
public showPreviousReplaceTerm() {
let previous;
if (this.replaceInput.value.length === 0) {
previous = this.replaceHistory.current();
} else {
this.replaceHistory.addIfNotPresent(this.replaceInput.value);
previous = this.replaceHistory.previous();
}
if (previous) {
this.replaceInput.value = previous;
}
}
public searchInputHasFocus(): boolean {
return this.searchInputBoxFocused.get();
}
......@@ -256,6 +283,12 @@ export class SearchWidget extends Widget {
this.searchHistory.add(this.searchInput.getValue());
}));
this._register(this.onReplaceValueChanged(() => {
if ((this.replaceHistory.current() !== this.replaceInput.value) && (this.replaceInput.value)) {
this.replaceHistory.add(this.replaceInput.value);
}
}));
this.searchInputFocusTracker = this._register(dom.trackFocus(this.searchInput.inputBox.inputElement));
this._register(this.searchInputFocusTracker.onDidFocus(() => {
this.searchInputBoxFocused.set(true);
......
......@@ -53,7 +53,7 @@ import { getMultiSelectedResources } from 'vs/workbench/parts/files/browser/file
import { Schemas } from 'vs/base/common/network';
import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { openSearchView, getSearchView, ReplaceAllInFolderAction, ReplaceAllAction, CloseReplaceAction, FocusNextInputAction, FocusPreviousInputAction, FocusNextSearchResultAction, FocusPreviousSearchResultAction, ReplaceInFilesAction, FindInFilesAction, FocusActiveEditorCommand, toggleCaseSensitiveCommand, ShowNextSearchTermAction, ShowPreviousSearchTermAction, toggleRegexCommand, ShowPreviousSearchIncludeAction, ShowNextSearchIncludeAction, CollapseDeepestExpandedLevelAction, toggleWholeWordCommand, RemoveAction, ReplaceAction, ClearSearchResultsAction, copyPathCommand, copyMatchCommand, copyAllCommand, ShowNextSearchExcludeAction, ShowPreviousSearchExcludeAction, clearHistoryCommand } from 'vs/workbench/parts/search/browser/searchActions';
import { openSearchView, getSearchView, ReplaceAllInFolderAction, ReplaceAllAction, CloseReplaceAction, FocusNextInputAction, FocusPreviousInputAction, FocusNextSearchResultAction, FocusPreviousSearchResultAction, ReplaceInFilesAction, FindInFilesAction, FocusActiveEditorCommand, toggleCaseSensitiveCommand, ShowNextSearchTermAction, ShowPreviousSearchTermAction, toggleRegexCommand, ShowPreviousSearchIncludeAction, ShowNextSearchIncludeAction, CollapseDeepestExpandedLevelAction, toggleWholeWordCommand, RemoveAction, ReplaceAction, ClearSearchResultsAction, copyPathCommand, copyMatchCommand, copyAllCommand, ShowNextSearchExcludeAction, ShowPreviousSearchExcludeAction, clearHistoryCommand, ShowNextReplaceTermAction, ShowPreviousReplaceTermAction } from 'vs/workbench/parts/search/browser/searchActions';
import { VIEW_ID, ISearchConfigurationProperties } from 'vs/platform/search/common/search';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
......@@ -493,6 +493,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextSearchTermAction, ShowNextSearchTermAction.ID, ShowNextSearchTermAction.LABEL, ShowNextFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchInputBoxFocusedKey)), 'Search: Show Next Search Term', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousSearchTermAction, ShowPreviousSearchTermAction.ID, ShowPreviousSearchTermAction.LABEL, ShowPreviousFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.SearchInputBoxFocusedKey)), 'Search: Show Previous Search Term', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextReplaceTermAction, ShowNextReplaceTermAction.ID, ShowNextReplaceTermAction.LABEL, ShowNextFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceInputBoxFocusedKey)), 'Search: Show Next Search Replace Term', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousReplaceTermAction, ShowPreviousReplaceTermAction.ID, ShowPreviousReplaceTermAction.LABEL, ShowPreviousFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceInputBoxFocusedKey)), 'Search: Show Previous Search Replace Term', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextSearchIncludeAction, ShowNextSearchIncludeAction.ID, ShowNextSearchIncludeAction.LABEL, ShowNextFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.PatternIncludesFocusedKey)), 'Search: Show Next Search Include Pattern', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousSearchIncludeAction, ShowPreviousSearchIncludeAction.ID, ShowPreviousSearchIncludeAction.LABEL, ShowPreviousFindTermKeybinding, ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.PatternIncludesFocusedKey)), 'Search: Show Previous Search Include Pattern', category);
......
......@@ -446,6 +446,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
const isHorizontal = (direction === Direction.Left || direction === Direction.Right);
const font = this._terminalService.configHelper.getFont();
// TODO: Support letter spacing and line height
const amount = isHorizontal ? font.charWidth : font.charHeight;
if (amount) {
this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, amount);
......
......@@ -45,6 +45,10 @@ export const TerminalCursorStyle = {
export const TERMINAL_CONFIG_SECTION = 'terminal.integrated';
export const DEFAULT_LETTER_SPACING = 0;
export const MINIMUM_LETTER_SPACING = -5;
export const DEFAULT_LINE_HEIGHT = 1.0;
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
export interface ITerminalConfiguration {
......@@ -67,6 +71,7 @@ export interface ITerminalConfiguration {
fontWeightBold: FontWeight;
// fontLigatures: boolean;
fontSize: number;
letterSpacing: number;
lineHeight: number;
setLocaleVariables: boolean;
scrollback: number;
......@@ -97,6 +102,7 @@ export interface ITerminalConfigHelper {
export interface ITerminalFont {
fontFamily: string;
fontSize: number;
letterSpacing: number;
lineHeight: number;
charWidth?: number;
charHeight?: number;
......
......@@ -13,7 +13,7 @@ import * as panel from 'vs/workbench/browser/panel';
import * as platform from 'vs/base/common/platform';
import * as terminalCommands from 'vs/workbench/parts/terminal/common/terminalCommands';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TerminalCursorStyle, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TerminalCursorStyle, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, DEFAULT_LINE_HEIGHT, DEFAULT_LETTER_SPACING } from 'vs/workbench/parts/terminal/common/terminal';
import { getTerminalDefaultShellUnixLike, getTerminalDefaultShellWindows } from 'vs/workbench/parts/terminal/node/terminal';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
......@@ -140,10 +140,15 @@ configurationRegistry.registerConfiguration({
'type': 'number',
'default': EDITOR_FONT_DEFAULTS.fontSize
},
'terminal.integrated.letterSpacing': {
'description': nls.localize('terminal.integrated.letterSpacing', "Controls the letter spacing of the terminal, this is an integer value which represents the amount of additional pixels to add between characters."),
'type': 'number',
'default': DEFAULT_LETTER_SPACING
},
'terminal.integrated.lineHeight': {
'description': nls.localize('terminal.integrated.lineHeight', "Controls the line height of the terminal, this number is multiplied by the terminal font size to get the actual line-height in pixels."),
'type': 'number',
'default': 1
'default': DEFAULT_LINE_HEIGHT
},
'terminal.integrated.fontWeight': {
'type': 'string',
......
......@@ -10,14 +10,12 @@ import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/ed
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITerminalConfiguration, ITerminalConfigHelper, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalConfiguration, ITerminalConfigHelper, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING } from 'vs/workbench/parts/terminal/common/terminal';
import Severity from 'vs/base/common/severity';
import { isFedora } from 'vs/workbench/parts/terminal/node/terminal';
import { Terminal as XTermTerminal } from 'vscode-xterm';
import { INotificationService } from 'vs/platform/notification/common/notification';
const DEFAULT_LINE_HEIGHT = 1.0;
const MINIMUM_FONT_SIZE = 6;
const MAXIMUM_FONT_SIZE = 25;
......@@ -50,7 +48,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
this.config = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
}
private _measureFont(fontFamily: string, fontSize: number, lineHeight: number): ITerminalFont {
private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont {
// Create charMeasureElement if it hasn't been created or if it was orphaned by its parent
if (!this._charMeasureElement || !this._charMeasureElement.parentElement) {
this._charMeasureElement = document.createElement('div');
......@@ -74,6 +72,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
this._lastFontMeasurement = {
fontFamily,
fontSize,
letterSpacing,
lineHeight,
charWidth: rect.width,
charHeight: Math.ceil(rect.height)
......@@ -98,12 +97,14 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
}
let fontSize = this._toInteger(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize);
const letterSpacing = this.config.letterSpacing ? Math.max(Math.floor(this.config.letterSpacing), MINIMUM_LETTER_SPACING) : DEFAULT_LETTER_SPACING;
const lineHeight = this.config.lineHeight ? Math.max(this.config.lineHeight, 1) : DEFAULT_LINE_HEIGHT;
if (excludeDimensions) {
return {
fontFamily,
fontSize,
letterSpacing,
lineHeight
};
}
......@@ -114,6 +115,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
return {
fontFamily,
fontSize,
letterSpacing,
lineHeight,
charHeight: xterm.charMeasure.height,
charWidth: xterm.charMeasure.width
......@@ -122,7 +124,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
}
// Fall back to measuring the font ourselves
return this._measureFont(fontFamily, fontSize, lineHeight);
return this._measureFont(fontFamily, fontSize, letterSpacing, lineHeight);
}
public setWorkspaceShellAllowed(isAllowed: boolean): void {
......
......@@ -185,7 +185,7 @@ export class TerminalInstance implements ITerminalInstance {
// order to be precise. font.charWidth/charHeight alone as insufficient
// when window.devicePixelRatio changes.
const scaledWidthAvailable = dimension.width * window.devicePixelRatio;
const scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio);
const scaledCharWidth = Math.floor(font.charWidth * window.devicePixelRatio) + font.letterSpacing;
this._cols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1);
const scaledHeightAvailable = dimension.height * window.devicePixelRatio;
......@@ -256,6 +256,7 @@ export class TerminalInstance implements ITerminalInstance {
fontWeight: this._configHelper.config.fontWeight,
fontWeightBold: this._configHelper.config.fontWeightBold,
fontSize: font.fontSize,
letterSpacing: font.letterSpacing,
lineHeight: font.lineHeight,
bellStyle: this._configHelper.config.enableBell ? 'sound' : 'none',
screenReaderMode: accessibilitySupport === 'on',
......@@ -848,6 +849,9 @@ export class TerminalInstance implements ITerminalInstance {
// Only apply these settings when the terminal is visible so that
// the characters are measured correctly.
if (this._isVisible) {
if (this._xterm.getOption('letterSpacing') !== font.letterSpacing) {
this._xterm.setOption('letterSpacing', font.letterSpacing);
}
if (this._xterm.getOption('lineHeight') !== font.lineHeight) {
this._xterm.setOption('lineHeight', font.lineHeight);
}
......
......@@ -25,7 +25,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { IUpdateService, State as UpdateState, StateType, IUpdate } from 'vs/platform/update/common/update';
import * as semver from 'semver';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { INotificationService, INotificationHandle } from 'vs/platform/notification/common/notification';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ReleaseNotesManager } from './releaseNotesEditor';
......@@ -150,10 +150,10 @@ class NeverShowAgain {
private readonly key: string;
readonly action = new Action(`neverShowAgain:${this.key}`, nls.localize('neveragain', "Don't Show Again"), undefined, true, (notification: IDisposable) => {
readonly action = new Action(`neverShowAgain:${this.key}`, nls.localize('neveragain', "Don't Show Again"), undefined, true, (notification: INotificationHandle) => {
// Hide notification
notification.dispose();
notification.close();
return TPromise.wrap(this.storageService.store(this.key, true, StorageScope.GLOBAL));
});
......@@ -194,14 +194,14 @@ export class Win3264BitContribution implements IWorkbenchContribution {
? Win3264BitContribution.INSIDER_URL
: Win3264BitContribution.URL;
notificationService.prompt(
const handle = notificationService.prompt(
severity.Info,
nls.localize('64bitisavailable', "{0} for 64-bit Windows is now available! Click [here]({1}) to learn more.", product.nameShort, url),
[{
label: nls.localize('neveragain', "Don't Show Again"),
isSecondary: true,
run: () => {
neverShowAgain.action.run();
neverShowAgain.action.run(handle);
neverShowAgain.action.dispose();
}
}]
......@@ -384,14 +384,14 @@ export class UpdateContribution implements IGlobalActivity {
return;
}
this.notificationService.prompt(
const handle = this.notificationService.prompt(
severity.Info,
nls.localize('updateInstalling', "{0} {1} is being installed in the background, we'll let you know when it's done.", product.nameLong, update.productVersion),
[{
label: nls.localize('neveragain', "Don't Show Again"),
isSecondary: true,
run: () => {
neverShowAgain.action.run();
neverShowAgain.action.run(handle);
neverShowAgain.action.dispose();
}
}]
......
......@@ -11,6 +11,7 @@ import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/e
import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { workspaceSettingsSchemaId, launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { isObject } from 'vs/base/common/types';
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
......@@ -131,7 +132,17 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten
}
for (let key in properties) {
const message = validateProperty(key);
if (message) {
delete properties[key];
extension.collector.warn(message);
continue;
}
const propertyConfiguration = configuration.properties[key];
if (!isObject(propertyConfiguration)) {
delete properties[key];
extension.collector.error(nls.localize('invalid.property', "'configuration.property' must be an object"));
continue;
}
if (propertyConfiguration.scope) {
if (propertyConfiguration.scope.toString() === 'application') {
propertyConfiguration.scope = ConfigurationScope.APPLICATION;
......@@ -144,10 +155,6 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten
propertyConfiguration.scope = ConfigurationScope.WINDOW;
}
propertyConfiguration.notMultiRootAdopted = !(extension.description.isBuiltin || (Array.isArray(extension.description.keywords) && extension.description.keywords.indexOf('multi-root ready') !== -1));
if (message) {
extension.collector.warn(message);
delete properties[key];
}
}
}
let subNodes = configuration.allOf;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Code } from '../../vscode/code';
export class QuickInput {
static QUICK_INPUT = '.quick-input-widget';
static QUICK_INPUT_INPUT = `${QuickInput.QUICK_INPUT} .quick-input-box input`;
static QUICK_INPUT_FOCUSED_ELEMENT = `${QuickInput.QUICK_INPUT} .quick-open-tree .monaco-tree-row.focused .monaco-highlighted-label`;
constructor(private code: Code) { }
async closeQuickInput(): Promise<void> {
await this.code.dispatchKeybinding('escape');
await this.waitForQuickInputClosed();
}
async waitForQuickInputOpened(retryCount?: number): Promise<void> {
await this.code.waitForActiveElement(QuickInput.QUICK_INPUT_INPUT, retryCount);
}
private async waitForQuickInputClosed(): Promise<void> {
await this.code.waitForElement(QuickInput.QUICK_INPUT, r => !!r && r.attributes.style.indexOf('display: none;') !== -1);
}
}
......@@ -30,8 +30,8 @@ export function setup() {
const app = this.app as Application;
await app.workbench.statusbar.clickOn(StatusBarElement.BRANCH_STATUS);
await app.workbench.quickopen.waitForQuickOpenOpened();
await app.workbench.quickopen.closeQuickOpen();
await app.workbench.quickinput.waitForQuickInputOpened();
await app.workbench.quickinput.closeQuickInput();
await app.workbench.quickopen.openFile('app.js');
await app.workbench.statusbar.clickOn(StatusBarElement.INDENTATION_STATUS);
......
......@@ -6,6 +6,7 @@
import { Explorer } from '../explorer/explorer';
import { ActivityBar } from '../activitybar/activityBar';
import { QuickOpen } from '../quickopen/quickopen';
import { QuickInput } from '../quickinput/quickinput';
import { Extensions } from '../extensions/extensions';
import { Search } from '../search/search';
import { Editor } from '../editor/editor';
......@@ -26,6 +27,7 @@ export interface Commands {
export class Workbench {
readonly quickopen: QuickOpen;
readonly quickinput: QuickInput;
readonly editors: Editors;
readonly explorer: Explorer;
readonly activitybar: ActivityBar;
......@@ -43,6 +45,7 @@ export class Workbench {
constructor(code: Code, userDataPath: string) {
this.editors = new Editors(code);
this.quickopen = new QuickOpen(code, this.editors);
this.quickinput = new QuickInput(code);
this.explorer = new Explorer(code, this.editors);
this.activitybar = new ActivityBar(code);
this.search = new Search(code);
......
......@@ -5380,9 +5380,9 @@ strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
sudo-prompt@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.0.0.tgz#a7b4a1ca6cbcca0e705b90a89dfc81d11034cba9"
sudo-prompt@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.0.tgz#bcd4aaacdb367b77b4bffcce1c658c2b1dd327f3"
sumchecker@^2.0.1:
version "2.0.2"
......@@ -5997,9 +5997,9 @@ vscode-textmate@^3.3.3:
fast-plist "^0.1.2"
oniguruma "^6.0.1"
vscode-xterm@3.5.0-beta1:
version "3.5.0-beta1"
resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.5.0-beta1.tgz#90b45c71aa188d0c6e1acbec18f587203d18dc2a"
vscode-xterm@3.4.0-beta3:
version "3.4.0-beta3"
resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.4.0-beta3.tgz#349db387bd3669ad4d6044a6ea6d699e6d649fb5"
vso-node-api@^6.1.2-preview:
version "6.1.2-preview"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册