Extract autosuggest editor to own class; bring into settings (#55924)

* Extract autosuggest editor to own class;
bring into settings

* Remove unused css. Fix clipping issue.

* Autosuggest -> suggest

* Remove rounded selections from suggest inputs
This commit is contained in:
Jackson Kearl 2018-08-10 12:47:51 -07:00 committed by GitHub
parent 753c832fdd
commit 5e37f92880
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 376 additions and 225 deletions

View file

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.suggest-input-container {
padding: 3px 4px 5px;
}
.suggest-input-container .monaco-editor-background,
.suggest-input-container .monaco-editor,
.suggest-input-container .mtk1 {
/* allow the embedded monaco to be styled from the outer context */
background-color: inherit;
color: inherit;
}
.suggest-input-container .suggest-input-placeholder {
position: absolute;
z-index: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;
margin-top: 2px;
margin-left: 1px;
}

View file

@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
export function getSimpleEditorOptions(): IEditorOptions {
return {
wordWrap: 'on',
overviewRulerLanes: 0,
glyphMargin: false,
lineNumbers: 'off',
folding: false,
selectOnLineNumbers: false,
hideCursorInOverviewRuler: true,
selectionHighlight: false,
scrollbar: {
horizontal: 'hidden'
},
lineDecorationsWidth: 0,
overviewRulerBorder: false,
scrollBeyondLastLine: false,
renderLineHighlight: 'none',
fixedOverflowWidgets: true,
acceptSuggestionOnEnter: 'smart',
minimap: {
enabled: false
}
};
}

View file

@ -0,0 +1,232 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!./media/suggestEnabledInput';
import { $, addClass, append, removeClass, Dimension } from 'vs/base/browser/dom';
import { chain, Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
import uri from 'vs/base/common/uri';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import * as modes from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { inputBackground, inputBorder, inputForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Component } from 'vs/workbench/common/component';
import { MenuPreventer } from 'vs/workbench/parts/codeEditor/browser/menuPreventer';
import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/browser/simpleEditorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
interface SuggestResultsProvider {
/**
* Provider function for suggestion results.
*
* @param query the full text of the input.
*/
provideResults: (query: string) => string[];
/**
* Trigger characters for this input. Suggestions will appear when one of these is typed,
* or upon `ctrl+space` triggering at a word boundary.
*
* Defaults to the empty array.
*/
triggerCharacters?: string[];
/**
* Defines the sorting function used when showing results.
*
* Defaults to the identity function.
*/
sortKey?: (result: string) => string;
}
interface SuggestEnabledInputOptions {
/**
* The text to show when no input is present.
*
* Defaults to the empty string.
*/
placeholderText?: string;
/**
* Context key tracking the focus state of this element
*/
focusContextKey?: IContextKey<boolean>;
}
export class SuggestEnabledInput extends Component {
private _onShouldFocusResults = new Emitter<void>();
readonly onShouldFocusResults: Event<void> = this._onShouldFocusResults.event;
private _onEnter = new Emitter<void>();
readonly onEnter: Event<void> = this._onEnter.event;
private _onInputDidChange = new Emitter<string>();
readonly onInputDidChange: Event<string> = this._onInputDidChange.event;
private disposables: IDisposable[] = [];
private inputWidget: CodeEditorWidget;
private stylingContainer: HTMLDivElement;
private placeholderText: HTMLDivElement;
constructor(
id: string,
parent: HTMLElement,
suggestionProvider: SuggestResultsProvider,
ariaLabel: string,
resourceHandle: string,
options: SuggestEnabledInputOptions,
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IModelService modelService: IModelService,
) {
super(id, themeService);
this.stylingContainer = append(parent, $('.suggest-input-container'));
this.placeholderText = append(this.stylingContainer, $('.suggest-input-placeholder', null, options.placeholderText || ''));
this.inputWidget = instantiationService.createInstance(CodeEditorWidget, this.stylingContainer,
mixinHTMLInputStyleOptions(getSimpleEditorOptions(), ariaLabel),
{
contributions: [SuggestController, SnippetController2, ContextMenuController, MenuPreventer],
isSimpleWidget: true,
});
let scopeHandle = uri.parse(resourceHandle);
this.inputWidget.setModel(modelService.createModel('', null, scopeHandle, true));
this.disposables.push(this.inputWidget.onDidPaste(() => this.setValue(this.getValue()))); // setter cleanses
this.disposables.push((this.inputWidget.onDidFocusEditorText(() => {
if (options.focusContextKey) { options.focusContextKey.set(true); }
addClass(this.stylingContainer, 'synthetic-focus');
})));
this.disposables.push((this.inputWidget.onDidBlurEditorText(() => {
if (options.focusContextKey) { options.focusContextKey.set(false); }
removeClass(this.stylingContainer, 'synthetic-focus');
})));
const onKeyDownMonaco = chain(this.inputWidget.onKeyDown);
onKeyDownMonaco.filter(e => e.keyCode === KeyCode.Enter).on(e => { e.preventDefault(); this._onEnter.fire(); }, this, this.disposables);
onKeyDownMonaco.filter(e => e.keyCode === KeyCode.DownArrow && (isMacintosh ? e.metaKey : e.ctrlKey)).on(() => this._onShouldFocusResults.fire(), this, this.disposables);
let preexistingContent = this.getValue();
this.disposables.push(this.inputWidget.getModel().onDidChangeContent(() => {
let content = this.getValue();
this.placeholderText.style.visibility = content ? 'hidden' : 'visible';
if (preexistingContent.trim() === content.trim()) { return; }
this._onInputDidChange.fire();
preexistingContent = content;
}));
let validatedSuggestProvider = {
provideResults: suggestionProvider.provideResults,
sortKey: suggestionProvider.sortKey || (a => a),
triggerCharacters: suggestionProvider.triggerCharacters || []
};
this.disposables.push(modes.SuggestRegistry.register({ scheme: scopeHandle.scheme, pattern: '**/' + scopeHandle.path, hasAccessToAllModels: true }, {
triggerCharacters: validatedSuggestProvider.triggerCharacters,
provideCompletionItems: (model: ITextModel, position: Position, _context: modes.SuggestContext) => {
let query = model.getValue();
let wordStart = query.lastIndexOf(' ', position.column - 1) + 1;
let alreadyTypedCount = position.column - wordStart - 1;
// dont show suggestions if the user has typed something, but hasn't used the trigger character
if (alreadyTypedCount > 0 && (validatedSuggestProvider.triggerCharacters).indexOf(query[wordStart]) === -1) { return { suggestions: [] }; }
return {
suggestions: suggestionProvider.provideResults(query).map(result => {
return {
label: result,
insertText: result,
overwriteBefore: alreadyTypedCount,
sortText: validatedSuggestProvider.sortKey(result),
type: <modes.SuggestionType>'keyword'
};
})
};
}
}));
}
public setValue(val: string) {
val = val.replace(/\s/g, ' ');
this.inputWidget.setValue(val);
this.inputWidget.setScrollTop(0);
this.inputWidget.setPosition(new Position(1, val.length + 1));
}
public getValue(): string {
return this.inputWidget.getValue();
}
public updateStyles(): void {
super.updateStyles();
this.stylingContainer.style.backgroundColor = this.getColor(inputBackground);
this.stylingContainer.style.color = this.getColor(inputForeground);
this.placeholderText.style.color = this.getColor(inputPlaceholderForeground);
const inputBorderColor = this.getColor(inputBorder);
this.stylingContainer.style.borderWidth = inputBorderColor ? '1px' : null;
this.stylingContainer.style.borderStyle = inputBorderColor ? 'solid' : null;
this.stylingContainer.style.borderColor = inputBorderColor;
let cursor = this.stylingContainer.getElementsByClassName('cursor')[0] as HTMLDivElement;
if (cursor) {
cursor.style.backgroundColor = this.getColor(inputForeground);
}
}
public focus(): void {
this.inputWidget.focus();
}
public layout(dimension: Dimension): void {
this.inputWidget.layout(dimension);
this.placeholderText.style.width = `${dimension.width}px`;
}
public selectAll(): void {
this.inputWidget.setSelection(new Range(1, 1, 1, this.getValue().length + 1));
}
dispose(): void {
this.disposables = dispose(this.disposables);
super.dispose();
}
}
function mixinHTMLInputStyleOptions(config: IEditorOptions, ariaLabel?: string): IEditorOptions {
config.fontSize = 13;
config.lineHeight = 22;
config.wordWrap = 'off';
config.scrollbar.vertical = 'hidden';
config.roundedSelection = false;
config.ariaLabel = ariaLabel || '';
config.renderIndentGuides = false;
config.cursorWidth = 1;
config.snippetSuggestions = 'none';
config.suggest = { filterGraceful: false };
config.fontFamily = ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif';
return config;
}

View file

@ -6,7 +6,7 @@
import './electron-browser/accessibility';
import './electron-browser/inspectKeybindings';
import './electron-browser/largeFileOptimizations';
import './electron-browser/menuPreventer';
import './browser/menuPreventer';
import './electron-browser/selectionClipboard';
import './electron-browser/textMate/inspectTMScopes';
import './electron-browser/toggleMinimap';

View file

@ -3,9 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
import { MenuPreventer } from 'vs/workbench/parts/codeEditor/electron-browser/menuPreventer';
import { MenuPreventer } from 'vs/workbench/parts/codeEditor/browser/menuPreventer';
import { SelectionClipboard } from 'vs/workbench/parts/codeEditor/electron-browser/selectionClipboard';
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
@ -24,29 +23,4 @@ export function getSimpleCodeEditorWidgetOptions(): ICodeEditorWidgetOptions {
TabCompletionController,
]
};
}
export function getSimpleEditorOptions(): IEditorOptions {
return {
wordWrap: 'on',
overviewRulerLanes: 0,
glyphMargin: false,
lineNumbers: 'off',
folding: false,
selectOnLineNumbers: false,
hideCursorInOverviewRuler: true,
selectionHighlight: false,
scrollbar: {
horizontal: 'hidden'
},
lineDecorationsWidth: 0,
overviewRulerBorder: false,
scrollBeyondLastLine: false,
renderLineHighlight: 'none',
fixedOverflowWidgets: true,
acceptSuggestionOnEnter: 'smart',
minimap: {
enabled: false
}
};
}
}

View file

@ -12,7 +12,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ICommandService } from 'vs/platform/commands/common/commands';
// Allowed Editor Contributions:
import { MenuPreventer } from 'vs/workbench/parts/codeEditor/electron-browser/menuPreventer';
import { MenuPreventer } from 'vs/workbench/parts/codeEditor/browser/menuPreventer';
import { SelectionClipboard } from 'vs/workbench/parts/codeEditor/electron-browser/selectionClipboard';
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';

View file

@ -33,7 +33,8 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions';
import { getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions';
import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/browser/simpleEditorOptions';
const $ = dom.$;
const IPrivateBreakpointWidgetService = createDecorator<IPrivateBreakpointWidgetService>('privateBreakopintWidgetService');

View file

@ -47,7 +47,8 @@ import { HistoryNavigator } from 'vs/base/common/history';
import { IHistoryNavigationWidget } from 'vs/base/browser/history';
import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/widget/browser/contextScopedHistoryWidget';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions';
import { getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions';
import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/browser/simpleEditorOptions';
const $ = dom.$;

View file

@ -12,7 +12,7 @@ export class Query {
this.value = value.trim();
}
static autocompletions(query: string): string[] {
static suggestions(query: string): string[] {
const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'sort', 'category', 'tag', 'ext'];
const subcommands = {
'sort': ['installs', 'rating', 'name'],

View file

@ -6,21 +6,18 @@
'use strict';
import 'vs/css!./media/extensionsViewlet';
import uri from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { localize } from 'vs/nls';
import { ThrottledDelayer, always } from 'vs/base/common/async';
import { TPromise } from 'vs/base/common/winjs.base';
import { isPromiseCanceledError, onUnexpectedError, create as createError } from 'vs/base/common/errors';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event as EventOf, Emitter, chain } from 'vs/base/common/event';
import { Event as EventOf, Emitter } from 'vs/base/common/event';
import { IAction } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { append, $, addClass, removeClass, toggleClass, Dimension } from 'vs/base/browser/dom';
import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@ -39,7 +36,6 @@ import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorG
import Severity from 'vs/base/common/severity';
import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { inputForeground, inputBackground, inputBorder, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views';
import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
@ -59,18 +55,7 @@ import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/e
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { SingleServerExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService';
import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IModelService } from 'vs/editor/common/services/modelService';
import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { ITextModel } from 'vs/editor/common/model';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu';
import { MenuPreventer } from 'vs/workbench/parts/codeEditor/electron-browser/menuPreventer';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { isMacintosh } from 'vs/base/common/platform';
import { SuggestEnabledInput } from 'vs/workbench/parts/codeEditor/browser/suggestEnabledInput';
interface SearchInputEvent extends Event {
target: HTMLInputElement;
@ -265,14 +250,12 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
private searchDelayer: ThrottledDelayer<any>;
private root: HTMLElement;
private searchBox: CodeEditorWidget;
private searchBox: SuggestEnabledInput;
private extensionsBox: HTMLElement;
private primaryActions: IAction[];
private secondaryActions: IAction[];
private groupByServerAction: IAction;
private disposables: IDisposable[] = [];
private monacoStyleContainer: HTMLDivElement;
private placeholderText: HTMLDivElement;
constructor(
@IPartService partService: IPartService,
@ -290,8 +273,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
@IContextKeyService contextKeyService: IContextKeyService,
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService,
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService,
@IModelService private modelService: IModelService,
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService
) {
super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
@ -315,28 +297,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
}
}, this, this.disposables);
modes.SuggestRegistry.register({ scheme: 'extensions', pattern: '**/searchinput', hasAccessToAllModels: true }, {
triggerCharacters: ['@'],
provideCompletionItems: (model: ITextModel, position: Position, _context: modes.SuggestContext) => {
const sortKey = (item: string) => {
if (item.indexOf(':') === -1) { return 'a'; }
else if (/ext:/.test(item) || /tag:/.test(item)) { return 'b'; }
else if (/sort:/.test(item)) { return 'c'; }
else { return 'd'; }
};
return {
suggestions: this.autoComplete(model.getValue(), position.column).map(item => (
{
label: item.fullText,
insertText: item.fullText,
overwriteBefore: item.overwrite,
sortText: sortKey(item.fullText),
type: <modes.SuggestionType>'keyword'
}))
};
}
});
}
create(parent: HTMLElement): TPromise<void> {
@ -344,51 +304,32 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
this.root = parent;
const header = append(this.root, $('.header'));
this.monacoStyleContainer = append(header, $('.monaco-container'));
this.searchBox = this.instantiationService.createInstance(CodeEditorWidget, this.monacoStyleContainer,
mixinHTMLInputStyleOptions(getSimpleEditorOptions(), localize('searchExtensions', "Search Extensions in Marketplace")),
{
isSimpleWidget: true, contributions: [
SuggestController,
SnippetController2,
ContextMenuController,
MenuPreventer
]
});
this.placeholderText = append(this.monacoStyleContainer, $('.search-placeholder', null, localize('searchExtensions', "Search Extensions in Marketplace")));
const placeholder = localize('searchExtensions', "Search Extensions in Marketplace");
this.searchBox = this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, header, {
triggerCharacters: ['@'],
sortKey: item => {
if (item.indexOf(':') === -1) { return 'a'; }
else if (/ext:/.test(item) || /tag:/.test(item)) { return 'b'; }
else if (/sort:/.test(item)) { return 'c'; }
else { return 'd'; }
},
provideResults: (query) => Query.suggestions(query)
}, placeholder, 'extensions:searchinput', { placeholderText: placeholder });
this.disposables.push(this.searchBox);
const _searchChange = new Emitter<string>();
this.onSearchChange = _searchChange.event;
this.searchBox.onInputDidChange(() => {
this.triggerSearch();
_searchChange.fire(this.searchBox.getValue());
}, this, this.disposables);
this.searchBox.onShouldFocusResults(() => this.focusListView(), this, this.disposables);
this.extensionsBox = append(this.root, $('.extensions'));
this.searchBox.setModel(this.modelService.createModel('', null, uri.parse('extensions:searchinput'), true));
this.disposables.push(this.searchBox.onDidPaste(() => {
let trimmed = this.searchBox.getValue().replace(/\s+/g, ' ');
this.searchBox.setValue(trimmed);
this.searchBox.setScrollTop(0);
this.searchBox.setPosition(new Position(1, trimmed.length + 1));
}));
this.disposables.push(this.searchBox.onDidFocusEditorText(() => addClass(this.monacoStyleContainer, 'synthetic-focus')));
this.disposables.push(this.searchBox.onDidBlurEditorText(() => removeClass(this.monacoStyleContainer, 'synthetic-focus')));
const onKeyDownMonaco = chain(this.searchBox.onKeyDown);
onKeyDownMonaco.filter(e => e.keyCode === KeyCode.Enter).on(e => e.preventDefault(), this, this.disposables);
onKeyDownMonaco.filter(e => e.keyCode === KeyCode.DownArrow && (isMacintosh ? e.metaKey : e.ctrlKey)).on(() => this.focusListView(), this, this.disposables);
const searchChangeEvent = new Emitter<string>();
this.onSearchChange = searchChangeEvent.event;
let existingContent = this.searchBox.getValue().trim();
this.disposables.push(this.searchBox.getModel().onDidChangeContent(() => {
this.placeholderText.style.visibility = this.searchBox.getValue() ? 'hidden' : 'visible';
let content = this.searchBox.getValue().trim();
if (existingContent === content) { return; }
this.triggerSearch();
searchChangeEvent.fire(content);
existingContent = content;
}));
return super.create(this.extensionsBox)
.then(() => this.extensionManagementService.getInstalled(LocalExtensionType.User))
.then(installed => {
@ -401,20 +342,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
public updateStyles(): void {
super.updateStyles();
this.monacoStyleContainer.style.backgroundColor = this.getColor(inputBackground);
this.monacoStyleContainer.style.color = this.getColor(inputForeground);
this.placeholderText.style.color = this.getColor(inputPlaceholderForeground);
const inputBorderColor = this.getColor(inputBorder);
this.monacoStyleContainer.style.borderWidth = inputBorderColor ? '1px' : null;
this.monacoStyleContainer.style.borderStyle = inputBorderColor ? 'solid' : null;
this.monacoStyleContainer.style.borderColor = inputBorderColor;
let cursor = this.monacoStyleContainer.getElementsByClassName('cursor')[0] as HTMLDivElement;
if (cursor) {
cursor.style.backgroundColor = this.getColor(inputForeground);
}
this.searchBox.updateStyles();
}
setVisible(visible: boolean): TPromise<void> {
@ -423,7 +351,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
if (isVisibilityChanged) {
if (visible) {
this.searchBox.focus();
this.searchBox.setSelection(new Range(1, 1, 1, this.searchBox.getValue().length + 1));
this.searchBox.selectAll();
}
}
});
@ -436,8 +364,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
layout(dimension: Dimension): void {
toggleClass(this.root, 'narrow', dimension.width <= 300);
this.searchBox.layout({ height: 20, width: dimension.width - 34 });
this.placeholderText.style.width = '' + (dimension.width - 30) + 'px';
super.layout(new Dimension(dimension.width, dimension.height - 38));
}
@ -493,7 +419,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
event.immediate = true;
this.searchBox.setValue(value);
this.searchBox.setPosition(new Position(1, value.length + 1));
}
private triggerSearch(immediate = false): void {
@ -501,7 +426,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
}
private normalizedQuery(): string {
return (this.searchBox.getValue() || '').replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:');
return this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:');
}
private doSearch(): TPromise<any> {
@ -539,16 +464,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
return this.instantiationService.createInstance(viewDescriptor.ctor, options) as ViewletPanel;
}
private autoComplete(query: string, position: number): { fullText: string, overwrite: number }[] {
let wordStart = query.lastIndexOf(' ', position - 1) + 1;
let alreadyTypedCount = position - wordStart - 1;
// dont show autosuggestions if the user has typed something, but hasn't used the trigger character
if (alreadyTypedCount > 0 && query[wordStart] !== '@') { return []; }
return Query.autocompletions(query).map(replacement => ({ fullText: replacement, overwrite: alreadyTypedCount }));
}
private count(): number {
return this.panels.reduce((count, view) => (<ExtensionsListView>view).count() + count, 0);
}
@ -690,18 +605,4 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
function mixinHTMLInputStyleOptions(config: IEditorOptions, ariaLabel?: string): IEditorOptions {
config.fontSize = 13;
config.lineHeight = 22;
config.wordWrap = 'off';
config.scrollbar.vertical = 'hidden';
config.ariaLabel = ariaLabel || '';
config.renderIndentGuides = false;
config.cursorWidth = 1;
config.snippetSuggestions = 'none';
config.suggest = { filterGraceful: false };
config.fontFamily = ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif';
return config;
}
}

View file

@ -202,32 +202,6 @@
opacity: 0.9;
}
.extensions-viewlet .header .monaco-container {
padding: 3px 4px 5px;
}
.extensions-viewlet .header .monaco-container .suggest-widget {
width: 275px;
}
.extensions-viewlet .header .monaco-container .monaco-editor-background,
.extensions-viewlet .header .monaco-container .monaco-editor,
.extensions-viewlet .header .monaco-container .mtk1 {
/* allow the embedded monaco to be styled from the outer context */
background-color: inherit;
color: inherit;
}
.extensions-viewlet .header .search-placeholder {
position: absolute;
z-index: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
pointer-events: none;
margin-top: 2px;
}
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark,
.vs-dark .extensions-viewlet > .extensions .monaco-list-row.disabled > .bookmark,
.vs .extensions-viewlet > .extensions .monaco-list-row.disabled > .extension > .icon,

View file

@ -142,10 +142,10 @@ suite('Extension query', () => {
});
test('autocomplete', () => {
Query.autocompletions('@sort:in').some(x => x === '@sort:installs ');
Query.autocompletions('@sort:installs').every(x => x !== '@sort:rating ');
Query.suggestions('@sort:in').some(x => x === '@sort:installs ');
Query.suggestions('@sort:installs').every(x => x !== '@sort:rating ');
Query.autocompletions('@category:blah').some(x => x === '@category:"extension packs" ');
Query.autocompletions('@category:"extension packs"').every(x => x !== '@category:formatters ');
Query.suggestions('@category:blah').some(x => x === '@category:"extension packs" ');
Query.suggestions('@category:"extension packs"').every(x => x !== '@category:formatters ');
});
});

View file

@ -39,24 +39,6 @@
margin-right: 7px;
}
.settings-editor > .settings-header > .search-container {
position: relative;
}
.settings-editor > .settings-header .search-container > .settings-search-input {
vertical-align: middle;
}
.settings-editor > .settings-header .search-container > .settings-search-input > .monaco-inputbox {
height: 30px;
width: 100%;
}
.settings-editor > .settings-header .search-container .settings-search-input > .monaco-inputbox .input {
font-size: 14px;
padding-left: 7px;
}
.settings-editor > .settings-header > .settings-header-controls {
height: 32px;
display: flex;

View file

@ -32,7 +32,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorOptions, IEditor } from 'vs/workbench/common/editor';
import { PreferencesEditor } from 'vs/workbench/parts/preferences/browser/preferencesEditor';
import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout';
import { ISettingsEditorViewState, MODIFIED_SETTING_TAG, ONLINE_SERVICES_SETTING_TAG, resolveExtensionsSettings, resolveSettingsTree, SearchResultIdx, SearchResultModel, SettingsRenderer, SettingsTree, SettingsTreeElement, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTree';
import { TOCRenderer, TOCTree, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree';
@ -40,6 +40,8 @@ import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW
import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences';
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { SuggestEnabledInput } from 'vs/workbench/parts/codeEditor/browser/suggestEnabledInput';
import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
const $ = DOM.$;
@ -51,7 +53,7 @@ export class SettingsEditor2 extends BaseEditor {
private rootElement: HTMLElement;
private headerContainer: HTMLElement;
private searchWidget: SearchWidget;
private searchWidget: SuggestEnabledInput;
private settingsTargetsWidget: SettingsTargetsWidget;
private toolbar: ToolBar;
@ -121,6 +123,7 @@ export class SettingsEditor2 extends BaseEditor {
this.createHeader(this.rootElement);
this.createBody(this.rootElement);
this.updateStyles();
}
setInput(input: SettingsEditor2Input, options: EditorOptions, token: CancellationToken): Thenable<void> {
@ -138,9 +141,12 @@ export class SettingsEditor2 extends BaseEditor {
}
layout(dimension: DOM.Dimension): void {
this.searchWidget.layout(dimension);
this.layoutTrees(dimension);
let innerWidth = dimension.width - 24 * 2; // 24px padding on left and right
let monacoWidth = (innerWidth > 1000 ? 1000 : innerWidth - 10);
this.searchWidget.layout({ height: 20, width: monacoWidth });
DOM.toggleClass(this.rootElement, 'narrow', dimension.width < 600);
this.delayRefreshOnLayout.trigger(() => this.refreshTreeAndMaintainFocus());
@ -162,7 +168,7 @@ export class SettingsEditor2 extends BaseEditor {
}
clearSearchResults(): void {
this.searchWidget.clear();
this.searchWidget.setValue('');
}
search(text: string): void {
@ -184,13 +190,20 @@ export class SettingsEditor2 extends BaseEditor {
previewTextLabel.textContent = localize('previewLabel', "This is a preview of our new settings editor");
const searchContainer = DOM.append(this.headerContainer, $('.search-container'));
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, searchContainer, {
ariaLabel: localize('SearchSettings.AriaLabel', "Search settings"),
placeholder: localize('SearchSettings.Placeholder', "Search settings"),
focusKey: this.searchFocusContextKey,
ariaLive: 'assertive'
}));
this._register(this.searchWidget.onDidChange(() => this.onSearchInputChanged()));
let searchBoxLabel = localize('SearchSettings.AriaLabel', "Search settings");
this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, searchContainer, {
triggerCharacters: ['@'],
provideResults: (query: string) => {
return ['@modified', '@tag:usesOnlineServices'].filter(tag => query.indexOf(tag) === -1).map(tag => tag + ' ');
}
}, searchBoxLabel, 'settingseditor:searchinput', {
placeholderText: searchBoxLabel,
focusContextKey: this.searchFocusContextKey,
// TODO: Aria-live
}));
this._register(this.searchWidget.onInputDidChange(() => this.onSearchInputChanged()));
const headerControlsContainer = DOM.append(this.headerContainer, $('.settings-header-controls'));
const targetWidgetContainer = DOM.append(headerControlsContainer, $('.settings-target-container'));
@ -818,6 +831,20 @@ export class SettingsEditor2 extends BaseEditor {
this.settingsTreeRenderer.updateWidth(dimension.width);
}
public updateStyles(): void {
super.updateStyles();
this.searchWidget.updateStyles();
}
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void> {
if (visible) {
this.searchWidget.focus();
this.searchWidget.selectAll();
}
return TPromise.as(super.setVisible(visible, group));
}
}
interface ISettingsToolbarContext {

View file

@ -35,6 +35,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { PreferencesSearchService } from 'vs/workbench/parts/preferences/electron-browser/preferencesSearch';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { Command } from 'vs/editor/browser/editorExtensions';
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest';
registerSingleton(IPreferencesSearchService, PreferencesSearchService);
@ -384,14 +385,14 @@ class FocusSettingsFileEditorCommand extends SettingsCommand {
}
const focusSettingsFileEditorCommand = new FocusSettingsFileEditorCommand({
id: SETTINGS_EDITOR_COMMAND_FOCUS_FILE,
precondition: CONTEXT_SETTINGS_SEARCH_FOCUS,
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_SEARCH_FOCUS, SuggestContext.Visible.toNegated()),
kbOpts: { primary: KeyCode.DownArrow, weight: KeybindingWeight.EditorContrib }
});
focusSettingsFileEditorCommand.register();
const focusSettingsFromSearchCommand = new FocusSettingsFileEditorCommand({
id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH,
precondition: CONTEXT_SETTINGS_SEARCH_FOCUS,
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_SEARCH_FOCUS, SuggestContext.Visible.toNegated()),
kbOpts: { primary: KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib }
});
focusSettingsFromSearchCommand.register();