mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
* Basic show fuzzy results below filtered results * Renders filtered settings without a group * Display both local filter and remote results in their own groups on the same page * Remove duplicates between local and remote results * Hide empty groups more completely, port score bounds change from master, fix NPE * Remove fuzzy settings search toggle lightbulb icon * Render all filtered groups as dynamically sized groups * Remove renderer knowledge of filtered/nlp * Update setting count on each search * Fix match ranges for default settings vs the editable settings model * Add ISearchProvider to fix layer breakage. Fix hidden areas for filtered groups * Fix result count and updating on editable side changes * Simplify model rendering - render all result groups dynamically, without allocating extra space * Fix @-searches, clean up filterSettings() code in Settings Model * Update renderers for editable prefs model * Fix up metadata and telemetry * Fix clearing the results by deleting the query * Fix duplicated commonlyUsed settings, and navigation order * Fix nlp results order, and allow scoring results * Remove unused memento * Match count tweaks * Remove obsolete padTo argument to pushGroups * Move searchResultGroup state from renderer to preferencesModels * Remove old fuzzy search prompt link * Fix NPE on filterResult * nlp/filter => remote/local * Remove "render" term from preferencesModels * Simplify settings editor model rendering - All groups are wrapped in braces for consistency When search isn't active, the search groups are removed from the editor model, not hidden by the renderer * Remove unread 'arrays' * Simplify hiding duplicate settings search results * Fix blinking on slow tokenization in search reuslts
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { onUnexpectedError, isPromiseCanceledError, getErrorMessage } from 'vs/base/common/errors';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Delayer, ThrottledDelayer } from 'vs/base/common/async';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
@@ -14,7 +14,6 @@ import { ArrayNavigator, INavigator } from 'vs/base/common/iterator';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { SideBySideEditorInput, EditorOptions, EditorInput } from 'vs/workbench/common/editor';
|
||||
import { Scope } from 'vs/workbench/common/memento';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
|
||||
import { IEditorControl, Position, Verbosity } from 'vs/platform/editor/common/editor';
|
||||
@@ -25,7 +24,7 @@ import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import {
|
||||
IPreferencesService, ISettingsGroup, ISetting, IFilterResult, IPreferencesSearchService,
|
||||
CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, ISettingsEditorModel, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, IFilterMetadata, IPreferencesSearchModel
|
||||
CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, ISettingsEditorModel, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, IFilterMetadata, ISearchProvider, ISearchResult
|
||||
} from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { SettingsEditorModel, DefaultSettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -108,14 +107,13 @@ export class PreferencesEditor extends BaseEditor {
|
||||
private headerContainer: HTMLElement;
|
||||
private searchWidget: SearchWidget;
|
||||
private sideBySidePreferencesWidget: SideBySidePreferencesWidget;
|
||||
private preferencesRenderers: PreferencesRenderers;
|
||||
private preferencesRenderers: PreferencesRenderersController;
|
||||
|
||||
private delayedFilterLogging: Delayer<void>;
|
||||
private filterThrottle: ThrottledDelayer<void>;
|
||||
private remoteSearchThrottle: ThrottledDelayer<void>;
|
||||
|
||||
private latestEmptyFilters: string[] = [];
|
||||
private lastFocusedWidget: SearchWidget | SideBySidePreferencesWidget = null;
|
||||
private memento: any;
|
||||
|
||||
constructor(
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@@ -131,8 +129,7 @@ export class PreferencesEditor extends BaseEditor {
|
||||
this.defaultSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(this.contextKeyService);
|
||||
this.focusSettingsContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(this.contextKeyService);
|
||||
this.delayedFilterLogging = new Delayer<void>(1000);
|
||||
this.filterThrottle = new ThrottledDelayer(200);
|
||||
this.memento = this.getMemento(storageService, Scope.WORKSPACE);
|
||||
this.remoteSearchThrottle = new ThrottledDelayer(200);
|
||||
}
|
||||
|
||||
public createEditor(parent: Builder): void {
|
||||
@@ -145,12 +142,8 @@ export class PreferencesEditor extends BaseEditor {
|
||||
ariaLabel: nls.localize('SearchSettingsWidget.AriaLabel', "Search settings"),
|
||||
placeholder: nls.localize('SearchSettingsWidget.Placeholder', "Search Settings"),
|
||||
focusKey: this.focusSettingsContextKey,
|
||||
showFuzzyToggle: true,
|
||||
showResultCount: true
|
||||
}));
|
||||
this.searchWidget.setFuzzyToggleVisible(this.preferencesSearchService.remoteSearchAllowed);
|
||||
this.searchWidget.fuzzyEnabled = this.memento['fuzzyEnabled'];
|
||||
this._register(this.preferencesSearchService.onRemoteSearchEnablementChanged(enabled => this.searchWidget.setFuzzyToggleVisible(enabled)));
|
||||
this._register(this.searchWidget.onDidChange(value => this.onInputChanged()));
|
||||
this._register(this.searchWidget.onFocus(() => this.lastFocusedWidget = this.searchWidget));
|
||||
this.lastFocusedWidget = this.searchWidget;
|
||||
@@ -160,12 +153,7 @@ export class PreferencesEditor extends BaseEditor {
|
||||
this._register(this.sideBySidePreferencesWidget.onFocus(() => this.lastFocusedWidget = this.sideBySidePreferencesWidget));
|
||||
this._register(this.sideBySidePreferencesWidget.onDidSettingsTargetChange(target => this.switchSettings(target)));
|
||||
|
||||
this.preferencesRenderers = this._register(new PreferencesRenderers(this.preferencesSearchService));
|
||||
|
||||
this._register(this.preferencesRenderers.onTriggeredFuzzy(() => {
|
||||
this.searchWidget.fuzzyEnabled = true;
|
||||
this.filterPreferences();
|
||||
}));
|
||||
this.preferencesRenderers = this._register(new PreferencesRenderersController(this.preferencesSearchService, this.telemetryService));
|
||||
|
||||
this._register(this.preferencesRenderers.onDidFilterResultsCountChange(count => this.showSearchResultsMessage(count)));
|
||||
}
|
||||
@@ -250,15 +238,17 @@ export class PreferencesEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
private onInputChanged(): void {
|
||||
if (this.searchWidget.fuzzyEnabled) {
|
||||
this.triggerThrottledFilter();
|
||||
} else {
|
||||
this.filterPreferences();
|
||||
}
|
||||
this.triggerThrottledSearch();
|
||||
this.localFilterPreferences();
|
||||
}
|
||||
|
||||
private triggerThrottledFilter(): void {
|
||||
this.filterThrottle.trigger(() => this.filterPreferences());
|
||||
private triggerThrottledSearch(): void {
|
||||
if (this.searchWidget.getValue()) {
|
||||
this.remoteSearchThrottle.trigger(() => this.remoteSearchPreferences());
|
||||
} else {
|
||||
// When clearing the input, update immediately to clear it
|
||||
this.remoteSearchPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
private switchSettings(target: SettingsTarget): void {
|
||||
@@ -278,17 +268,29 @@ export class PreferencesEditor extends BaseEditor {
|
||||
});
|
||||
}
|
||||
|
||||
private filterPreferences(): TPromise<void> {
|
||||
this.memento['fuzzyEnabled'] = this.searchWidget.fuzzyEnabled;
|
||||
const filter = this.searchWidget.getValue().trim();
|
||||
return this.preferencesRenderers.filterPreferences({ filter, fuzzy: this.searchWidget.fuzzyEnabled }).then(result => {
|
||||
private remoteSearchPreferences(): TPromise<void> {
|
||||
const query = this.searchWidget.getValue().trim();
|
||||
return this.preferencesRenderers.remoteSearchPreferences(query).then(result => {
|
||||
this.onSearchResult(query, result);
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
|
||||
private localFilterPreferences(): TPromise<void> {
|
||||
const query = this.searchWidget.getValue().trim();
|
||||
return this.preferencesRenderers.localFilterPreferences(query).then(result => {
|
||||
this.onSearchResult(query, result);
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
|
||||
private onSearchResult(filter: string, result: { count: number, metadata: IFilterMetadata }): void {
|
||||
if (result) {
|
||||
this.showSearchResultsMessage(result.count);
|
||||
if (result.count === 0) {
|
||||
this.latestEmptyFilters.push(filter);
|
||||
}
|
||||
this.preferencesRenderers.focusFirst();
|
||||
|
||||
this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(filter, result.metadata));
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
private showSearchResultsMessage(count: number): void {
|
||||
@@ -372,35 +374,32 @@ class SettingsNavigator implements INavigator<ISetting> {
|
||||
}
|
||||
}
|
||||
|
||||
interface ISearchCriteria {
|
||||
filter: string;
|
||||
fuzzy: boolean;
|
||||
interface IFilterOrSearchResult {
|
||||
count: number;
|
||||
metadata: IFilterMetadata;
|
||||
}
|
||||
|
||||
class PreferencesRenderers extends Disposable {
|
||||
class PreferencesRenderersController extends Disposable {
|
||||
|
||||
private _defaultPreferencesRenderer: IPreferencesRenderer<ISetting>;
|
||||
private _defaultPreferencesRendererDisposables: IDisposable[] = [];
|
||||
|
||||
private _defaultPreferencesFilterResult: IFilterResult;
|
||||
private _editablePreferencesFilterResult: IFilterResult;
|
||||
|
||||
private _editablePreferencesRenderer: IPreferencesRenderer<ISetting>;
|
||||
private _editablePreferencesRendererDisposables: IDisposable[] = [];
|
||||
|
||||
private _settingsNavigator: SettingsNavigator;
|
||||
private _filtersInProgress: TPromise<any>[];
|
||||
private _searchCriteria: ISearchCriteria;
|
||||
private _currentSearchModel: IPreferencesSearchModel;
|
||||
|
||||
private _onTriggeredFuzzy: Emitter<void> = this._register(new Emitter<void>());
|
||||
public onTriggeredFuzzy: Event<void> = this._onTriggeredFuzzy.event;
|
||||
private _currentLocalSearchProvider: ISearchProvider;
|
||||
private _currentRemoteSearchProvider: ISearchProvider;
|
||||
private _lastQuery: string;
|
||||
|
||||
private _onDidFilterResultsCountChange: Emitter<number> = this._register(new Emitter<number>());
|
||||
public onDidFilterResultsCountChange: Event<number> = this._onDidFilterResultsCountChange.event;
|
||||
|
||||
constructor(
|
||||
private preferencesSearchService: IPreferencesSearchService
|
||||
private preferencesSearchService: IPreferencesSearchService,
|
||||
private telemetryService: ITelemetryService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -423,9 +422,6 @@ class PreferencesRenderers extends Disposable {
|
||||
this._defaultPreferencesRenderer.onUpdatePreference(({ key, value, source, index }) => this._updatePreference(key, value, source, index, this._editablePreferencesRenderer), this, this._defaultPreferencesRendererDisposables);
|
||||
this._defaultPreferencesRenderer.onFocusPreference(preference => this._focusPreference(preference, this._editablePreferencesRenderer), this, this._defaultPreferencesRendererDisposables);
|
||||
this._defaultPreferencesRenderer.onClearFocusPreference(preference => this._clearFocus(preference, this._editablePreferencesRenderer), this, this._defaultPreferencesRendererDisposables);
|
||||
if (this._defaultPreferencesRenderer.onTriggeredFuzzy) {
|
||||
this._register(this._defaultPreferencesRenderer.onTriggeredFuzzy(() => this._onTriggeredFuzzy.fire()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -435,44 +431,47 @@ class PreferencesRenderers extends Disposable {
|
||||
this._editablePreferencesRenderer = editableSettingsRenderer;
|
||||
this._editablePreferencesRendererDisposables = dispose(this._editablePreferencesRendererDisposables);
|
||||
if (this._editablePreferencesRenderer) {
|
||||
(<ISettingsEditorModel>this._editablePreferencesRenderer.preferencesModel).onDidChangeGroups(() => {
|
||||
if (this._currentSearchModel) {
|
||||
this._filterEditablePreferences()
|
||||
.then(() => {
|
||||
const count = this.consolidateAndUpdate();
|
||||
this._onDidFilterResultsCountChange.fire(count);
|
||||
});
|
||||
}
|
||||
}, this, this._editablePreferencesRendererDisposables);
|
||||
(<ISettingsEditorModel>this._editablePreferencesRenderer.preferencesModel)
|
||||
.onDidChangeGroups(this._onEditableContentDidChange, this, this._editablePreferencesRendererDisposables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterPreferences(criteria: ISearchCriteria): TPromise<{ count: number, metadata: IFilterMetadata }> {
|
||||
this._searchCriteria = criteria;
|
||||
async _onEditableContentDidChange(): TPromise<void> {
|
||||
await this.localFilterPreferences(this._lastQuery, true);
|
||||
await this.remoteSearchPreferences(this._lastQuery, true);
|
||||
}
|
||||
|
||||
remoteSearchPreferences(query: string, updateCurrentResults?: boolean): TPromise<IFilterOrSearchResult> {
|
||||
this._currentRemoteSearchProvider = (updateCurrentResults && this._currentRemoteSearchProvider) || this.preferencesSearchService.getRemoteSearchProvider(query);
|
||||
return this.filterOrSearchPreferences(query, this._currentRemoteSearchProvider, 'nlpResult', nls.localize('nlpResult', "Natural Language Results"));
|
||||
}
|
||||
|
||||
localFilterPreferences(query: string, updateCurrentResults?: boolean): TPromise<IFilterOrSearchResult> {
|
||||
this._currentLocalSearchProvider = (updateCurrentResults && this._currentLocalSearchProvider) || this.preferencesSearchService.getLocalSearchProvider(query);
|
||||
return this.filterOrSearchPreferences(query, this._currentLocalSearchProvider, 'filterResult', nls.localize('filterResult', "Filtered Results"));
|
||||
}
|
||||
|
||||
filterOrSearchPreferences(query: string, searchProvider: ISearchProvider, groupId: string, groupLabel: string): TPromise<IFilterOrSearchResult> {
|
||||
this._lastQuery = query;
|
||||
if (this._filtersInProgress) {
|
||||
// Resolved/rejected promises have no .cancel()
|
||||
this._filtersInProgress.forEach(p => p.cancel && p.cancel());
|
||||
}
|
||||
|
||||
this._currentSearchModel = this.preferencesSearchService.startSearch(this._searchCriteria.filter, criteria.fuzzy);
|
||||
this._filtersInProgress = [this._filterDefaultPreferences(), this._filterEditablePreferences()];
|
||||
this._filtersInProgress = [
|
||||
this._filterOrSearchPreferences(query, this.defaultPreferencesRenderer, searchProvider, groupId, groupLabel),
|
||||
this._filterOrSearchPreferences(query, this.editablePreferencesRenderer, searchProvider, groupId, groupLabel)];
|
||||
|
||||
return TPromise.join<IFilterResult>(this._filtersInProgress).then(() => {
|
||||
const count = this.consolidateAndUpdate();
|
||||
return { count, metadata: this._defaultPreferencesFilterResult && this._defaultPreferencesFilterResult.metadata };
|
||||
return TPromise.join(this._filtersInProgress).then(results => {
|
||||
this._filtersInProgress = null;
|
||||
const [defaultFilterResult, editableFilterResult] = results;
|
||||
|
||||
const count = this.consolidateAndUpdate(defaultFilterResult, editableFilterResult);
|
||||
return { count, metadata: defaultFilterResult && defaultFilterResult.metadata };
|
||||
});
|
||||
}
|
||||
|
||||
focusFirst(): void {
|
||||
// Focus first match in both renderers
|
||||
this._focusPreference(this._getFirstSettingFromTheGroups(this._defaultPreferencesFilterResult ? this._defaultPreferencesFilterResult.filteredGroups : []), this._defaultPreferencesRenderer);
|
||||
this._focusPreference(this._getFirstSettingFromTheGroups(this._editablePreferencesFilterResult ? this._editablePreferencesFilterResult.filteredGroups : []), this._editablePreferencesRenderer);
|
||||
|
||||
this._settingsNavigator.first(); // Move to first
|
||||
}
|
||||
|
||||
focusNextPreference(forward: boolean = true) {
|
||||
if (!this._settingsNavigator) {
|
||||
return;
|
||||
@@ -483,56 +482,61 @@ class PreferencesRenderers extends Disposable {
|
||||
this._focusPreference(setting, this._editablePreferencesRenderer);
|
||||
}
|
||||
|
||||
private _filterDefaultPreferences(): TPromise<void> {
|
||||
if (this._searchCriteria && this._defaultPreferencesRenderer) {
|
||||
return this._filterPreferences(this._searchCriteria, this._defaultPreferencesRenderer, this._currentSearchModel)
|
||||
.then(filterResult => { this._defaultPreferencesFilterResult = filterResult; });
|
||||
private _filterOrSearchPreferences(filter: string, preferencesRenderer: IPreferencesRenderer<ISetting>, provider: ISearchProvider, groupId: string, groupLabel: string): TPromise<IFilterResult> {
|
||||
if (preferencesRenderer) {
|
||||
const model = <ISettingsEditorModel>preferencesRenderer.preferencesModel;
|
||||
const searchP = provider ? provider.searchModel(model) : TPromise.wrap(null);
|
||||
return searchP
|
||||
.then<ISearchResult>(null, err => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
return null;
|
||||
} else {
|
||||
/* __GDPR__
|
||||
"defaultSettings.searchError" : {
|
||||
"message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
const message = getErrorMessage(err);
|
||||
this.telemetryService.publicLog('defaultSettings.searchError', { message });
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.then(searchResult => {
|
||||
const filterResult = searchResult ?
|
||||
model.updateResultGroup(groupId, {
|
||||
id: groupId,
|
||||
label: groupLabel,
|
||||
result: searchResult
|
||||
}) :
|
||||
model.updateResultGroup(groupId, null);
|
||||
|
||||
if (filterResult) {
|
||||
filterResult.query = filter;
|
||||
}
|
||||
|
||||
preferencesRenderer.filterPreferences(filterResult);
|
||||
return filterResult;
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.wrap(null);
|
||||
}
|
||||
|
||||
private _filterEditablePreferences(): TPromise<void> {
|
||||
if (this._searchCriteria && this._editablePreferencesRenderer) {
|
||||
return this._filterPreferences(this._searchCriteria, this._editablePreferencesRenderer, this._currentSearchModel)
|
||||
.then(filterResult => { this._editablePreferencesFilterResult = filterResult; });
|
||||
}
|
||||
return TPromise.wrap(null);
|
||||
}
|
||||
private consolidateAndUpdate(defaultFilterResult: IFilterResult, editableFilterResult: IFilterResult): number {
|
||||
const defaultPreferencesFilteredGroups = defaultFilterResult ? defaultFilterResult.filteredGroups : this._getAllPreferences(this._defaultPreferencesRenderer);
|
||||
const editablePreferencesFilteredGroups = editableFilterResult ? editableFilterResult.filteredGroups : this._getAllPreferences(this._editablePreferencesRenderer);
|
||||
const consolidatedSettings = this._consolidateSettings(editablePreferencesFilteredGroups, defaultPreferencesFilteredGroups);
|
||||
|
||||
private _getFirstSettingFromTheGroups(allGroups: ISettingsGroup[]): ISetting {
|
||||
if (allGroups.length) {
|
||||
if (allGroups[0].sections.length) {
|
||||
return allGroups[0].sections[0].settings[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
this._settingsNavigator = new SettingsNavigator(this._lastQuery ? consolidatedSettings : []);
|
||||
const count = consolidatedSettings.length;
|
||||
this._onDidFilterResultsCountChange.fire(count);
|
||||
return count;
|
||||
}
|
||||
|
||||
private _getAllPreferences(preferencesRenderer: IPreferencesRenderer<ISetting>): ISettingsGroup[] {
|
||||
return preferencesRenderer ? (<ISettingsEditorModel>preferencesRenderer.preferencesModel).settingsGroups : [];
|
||||
}
|
||||
|
||||
private _filterPreferences(searchCriteria: ISearchCriteria, preferencesRenderer: IPreferencesRenderer<ISetting>, searchModel: IPreferencesSearchModel): TPromise<IFilterResult> {
|
||||
if (preferencesRenderer && searchCriteria) {
|
||||
const prefSearchP = searchModel.filterPreferences(<ISettingsEditorModel>preferencesRenderer.preferencesModel);
|
||||
|
||||
return prefSearchP.then(filterResult => {
|
||||
preferencesRenderer.filterPreferences(filterResult, this.preferencesSearchService.remoteSearchAllowed);
|
||||
return filterResult;
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private consolidateAndUpdate(): number {
|
||||
const defaultPreferencesFilteredGroups = this._defaultPreferencesFilterResult ? this._defaultPreferencesFilterResult.filteredGroups : this._getAllPreferences(this._defaultPreferencesRenderer);
|
||||
const editablePreferencesFilteredGroups = this._editablePreferencesFilterResult ? this._editablePreferencesFilterResult.filteredGroups : this._getAllPreferences(this._editablePreferencesRenderer);
|
||||
const consolidatedSettings = this._consolidateSettings(editablePreferencesFilteredGroups, defaultPreferencesFilteredGroups);
|
||||
|
||||
this._settingsNavigator = new SettingsNavigator(this._searchCriteria.filter ? consolidatedSettings : []);
|
||||
return consolidatedSettings.length;
|
||||
}
|
||||
|
||||
private _focusPreference(preference: ISetting, preferencesRenderer: IPreferencesRenderer<ISetting>): void {
|
||||
if (preference && preferencesRenderer) {
|
||||
preferencesRenderer.focusPreference(preference);
|
||||
@@ -553,7 +557,7 @@ class PreferencesRenderers extends Disposable {
|
||||
|
||||
private _consolidateSettings(editableSettingsGroups: ISettingsGroup[], defaultSettingsGroups: ISettingsGroup[]): ISetting[] {
|
||||
const editableSettings = this._flatten(editableSettingsGroups);
|
||||
const defaultSettings = this._flatten(defaultSettingsGroups).filter(secondarySetting => !editableSettings.some(primarySetting => primarySetting.key === secondarySetting.key));
|
||||
const defaultSettings = this._flatten(defaultSettingsGroups).filter(secondarySetting => editableSettings.every(primarySetting => primarySetting.key !== secondarySetting.key));
|
||||
return [...defaultSettings, ...editableSettings];
|
||||
}
|
||||
|
||||
@@ -564,6 +568,7 @@ class PreferencesRenderers extends Disposable {
|
||||
settings.push(...section.settings);
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { tail } from 'vs/base/common/arrays';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
@@ -42,13 +41,12 @@ export interface IPreferencesRenderer<T> extends IDisposable {
|
||||
onFocusPreference: Event<T>;
|
||||
onClearFocusPreference: Event<T>;
|
||||
onUpdatePreference?: Event<{ key: string, value: any, source: T, index: number }>;
|
||||
onTriggeredFuzzy?: Event<void>;
|
||||
|
||||
render(): void;
|
||||
updatePreference(key: string, value: any, source: T, index: number): void;
|
||||
filterPreferences(filterResult: IFilterResult, fuzzySearchAvailable: boolean): void;
|
||||
focusPreference(setting: T): void;
|
||||
clearFocus(setting: T): void;
|
||||
filterPreferences(filterResult: IFilterResult): void;
|
||||
}
|
||||
|
||||
export class UserSettingsRenderer extends Disposable implements IPreferencesRenderer<ISetting> {
|
||||
@@ -162,6 +160,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.preferencesModel.getPreference(key);
|
||||
}
|
||||
|
||||
@@ -247,6 +246,8 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
|
||||
private hiddenAreasRenderer: HiddenAreasRenderer;
|
||||
private editSettingActionRenderer: EditSettingRenderer;
|
||||
private feedbackWidgetRenderer: FeedbackWidgetRenderer;
|
||||
private bracesHidingRenderer: BracesHidingRenderer;
|
||||
private filterResult: IFilterResult;
|
||||
|
||||
private _onUpdatePreference: Emitter<{ key: string, value: any, source: ISetting, index: number }> = new Emitter<{ key: string, value: any, source: ISetting, index: number }>();
|
||||
public readonly onUpdatePreference: Event<{ key: string, value: any, source: ISetting, index: number }> = this._onUpdatePreference.event;
|
||||
@@ -257,10 +258,6 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
|
||||
private _onClearFocusPreference: Emitter<ISetting> = new Emitter<ISetting>();
|
||||
public readonly onClearFocusPreference: Event<ISetting> = this._onClearFocusPreference.event;
|
||||
|
||||
public readonly onTriggeredFuzzy: Event<void>;
|
||||
|
||||
private filterResult: IFilterResult;
|
||||
|
||||
constructor(protected editor: ICodeEditor, public readonly preferencesModel: DefaultSettingsEditorModel,
|
||||
@IPreferencesService protected preferencesService: IPreferencesService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService
|
||||
@@ -272,14 +269,12 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
|
||||
this.filteredMatchesRenderer = this._register(instantiationService.createInstance(FilteredMatchesRenderer, editor));
|
||||
this.editSettingActionRenderer = this._register(instantiationService.createInstance(EditSettingRenderer, editor, preferencesModel, this.settingHighlighter));
|
||||
this.feedbackWidgetRenderer = this._register(instantiationService.createInstance(FeedbackWidgetRenderer, editor));
|
||||
const parenthesisHidingRenderer = this._register(instantiationService.createInstance(StaticContentHidingRenderer, editor, preferencesModel));
|
||||
this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, parenthesisHidingRenderer]));
|
||||
this.bracesHidingRenderer = this._register(instantiationService.createInstance(BracesHidingRenderer, editor, preferencesModel));
|
||||
this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, this.bracesHidingRenderer]));
|
||||
|
||||
this._register(this.editSettingActionRenderer.onUpdateSetting(e => this._onUpdatePreference.fire(e)));
|
||||
this._register(this.settingsGroupTitleRenderer.onHiddenAreasChanged(() => this.hiddenAreasRenderer.render()));
|
||||
this._register(preferencesModel.onDidChangeGroups(() => this.render()));
|
||||
|
||||
this.onTriggeredFuzzy = this.settingsHeaderRenderer.onClick;
|
||||
}
|
||||
|
||||
public get associatedPreferencesModel(): IPreferencesEditorModel<ISetting> {
|
||||
@@ -296,18 +291,20 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
|
||||
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel);
|
||||
this.feedbackWidgetRenderer.render(null);
|
||||
this.settingHighlighter.clear(true);
|
||||
this.bracesHidingRenderer.render(null, this.preferencesModel.settingsGroups);
|
||||
this.settingsGroupTitleRenderer.showGroup(0);
|
||||
this.hiddenAreasRenderer.render();
|
||||
}
|
||||
|
||||
public filterPreferences(filterResult: IFilterResult, fuzzySearchAvailable: boolean): void {
|
||||
public filterPreferences(filterResult: IFilterResult): void {
|
||||
this.filterResult = filterResult;
|
||||
if (filterResult) {
|
||||
this.filteredMatchesRenderer.render(filterResult, this.preferencesModel.settingsGroups);
|
||||
this.settingsGroupTitleRenderer.render(filterResult.filteredGroups);
|
||||
this.feedbackWidgetRenderer.render(filterResult);
|
||||
this.settingsHeaderRenderer.render(filterResult, fuzzySearchAvailable);
|
||||
this.settingsHeaderRenderer.render(filterResult);
|
||||
this.settingHighlighter.clear(true);
|
||||
this.bracesHidingRenderer.render(filterResult, this.preferencesModel.settingsGroups);
|
||||
this.editSettingActionRenderer.render(filterResult.filteredGroups, this._associatedPreferencesModel);
|
||||
} else {
|
||||
this.settingHighlighter.clear(true);
|
||||
@@ -316,6 +313,7 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
|
||||
this.settingsHeaderRenderer.render(null);
|
||||
this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups);
|
||||
this.settingsGroupTitleRenderer.showGroup(0);
|
||||
this.bracesHidingRenderer.render(null, this.preferencesModel.settingsGroups);
|
||||
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel);
|
||||
}
|
||||
|
||||
@@ -372,45 +370,64 @@ export interface HiddenAreasProvider {
|
||||
hiddenAreas: IRange[];
|
||||
}
|
||||
|
||||
export class StaticContentHidingRenderer extends Disposable implements HiddenAreasProvider {
|
||||
export class BracesHidingRenderer extends Disposable implements HiddenAreasProvider {
|
||||
private _result: IFilterResult;
|
||||
private _settingsGroups: ISettingsGroup[];
|
||||
|
||||
constructor(private editor: ICodeEditor, private settingsEditorModel: ISettingsEditorModel
|
||||
) {
|
||||
constructor(private editor: ICodeEditor) {
|
||||
super();
|
||||
}
|
||||
|
||||
get hiddenAreas(): IRange[] {
|
||||
const model = this.editor.getModel();
|
||||
render(result: IFilterResult, settingsGroups: ISettingsGroup[]): void {
|
||||
this._result = result;
|
||||
this._settingsGroups = settingsGroups;
|
||||
}
|
||||
|
||||
// Hide extra chars for "search results" and "commonly used" groups
|
||||
const settingsGroups = this.settingsEditorModel.settingsGroups;
|
||||
const lastGroup = tail(settingsGroups);
|
||||
return [
|
||||
get hiddenAreas(): IRange[] {
|
||||
// Opening square brace
|
||||
const hiddenAreas = [
|
||||
{
|
||||
startLineNumber: 1,
|
||||
startColumn: model.getLineMinColumn(1),
|
||||
startColumn: 1,
|
||||
endLineNumber: 2,
|
||||
endColumn: model.getLineMaxColumn(2)
|
||||
},
|
||||
{
|
||||
startLineNumber: settingsGroups[0].range.endLineNumber + 1,
|
||||
startColumn: model.getLineMinColumn(settingsGroups[0].range.endLineNumber + 1),
|
||||
endLineNumber: settingsGroups[0].range.endLineNumber + 4,
|
||||
endColumn: model.getLineMaxColumn(settingsGroups[0].range.endLineNumber + 4)
|
||||
},
|
||||
{
|
||||
startLineNumber: lastGroup.range.endLineNumber + 1,
|
||||
startColumn: model.getLineMinColumn(lastGroup.range.endLineNumber + 1),
|
||||
endLineNumber: Math.min(model.getLineCount(), lastGroup.range.endLineNumber + 4),
|
||||
endColumn: model.getLineMaxColumn(Math.min(model.getLineCount(), lastGroup.range.endLineNumber + 4))
|
||||
},
|
||||
{
|
||||
startLineNumber: model.getLineCount() - 1,
|
||||
startColumn: model.getLineMinColumn(model.getLineCount() - 1),
|
||||
endLineNumber: model.getLineCount(),
|
||||
endColumn: model.getLineMaxColumn(model.getLineCount())
|
||||
endColumn: 1
|
||||
}
|
||||
];
|
||||
|
||||
const hideBraces = group => {
|
||||
// Opening curly brace
|
||||
hiddenAreas.push({
|
||||
startLineNumber: group.range.startLineNumber - 3,
|
||||
startColumn: 1,
|
||||
endLineNumber: group.range.startLineNumber - 3,
|
||||
endColumn: 1
|
||||
});
|
||||
|
||||
// Closing curly brace
|
||||
hiddenAreas.push({
|
||||
startLineNumber: group.range.endLineNumber + 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: group.range.endLineNumber + 4,
|
||||
endColumn: 1
|
||||
});
|
||||
};
|
||||
|
||||
this._settingsGroups.forEach(hideBraces);
|
||||
if (this._result) {
|
||||
this._result.filteredGroups.forEach(hideBraces);
|
||||
}
|
||||
|
||||
// Closing square brace
|
||||
const lineCount = this.editor.getModel().getLineCount();
|
||||
hiddenAreas.push({
|
||||
startLineNumber: lineCount,
|
||||
startColumn: 1,
|
||||
endLineNumber: lineCount,
|
||||
endColumn: 1
|
||||
});
|
||||
|
||||
|
||||
return hiddenAreas;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -426,10 +443,9 @@ class DefaultSettingsHeaderRenderer extends Disposable {
|
||||
this.onClick = this.settingsHeaderWidget.onClick;
|
||||
}
|
||||
|
||||
public render(filterResult: IFilterResult, fuzzySearchAvailable = false) {
|
||||
public render(filterResult: IFilterResult) {
|
||||
const hasSettings = !filterResult || filterResult.filteredGroups.length > 0;
|
||||
const promptFuzzy = fuzzySearchAvailable && filterResult && !filterResult.metadata;
|
||||
this.settingsHeaderWidget.toggleMessage(hasSettings, promptFuzzy);
|
||||
this.settingsHeaderWidget.toggleMessage(hasSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,7 +744,7 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr
|
||||
this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, result.matches.map(match => this.createDecoration(match, model)));
|
||||
});
|
||||
} else {
|
||||
this.hiddenAreas = this.computeHiddenRanges(allSettingsGroups, allSettingsGroups, model);
|
||||
this.hiddenAreas = this.computeHiddenRanges(null, allSettingsGroups, model);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,67 +755,26 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'findMatch'
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private computeHiddenRanges(filteredGroups: ISettingsGroup[], allSettingsGroups: ISettingsGroup[], model: ITextModel): IRange[] {
|
||||
// Hide the contents of hidden groups
|
||||
const notMatchesRanges: IRange[] = [];
|
||||
for (const group of allSettingsGroups) {
|
||||
const filteredGroup = filteredGroups.filter(g => g.title === group.title)[0];
|
||||
if (!filteredGroup || filteredGroup.sections.every(sect => sect.settings.length === 0)) {
|
||||
if (filteredGroups) {
|
||||
allSettingsGroups.forEach((group, i) => {
|
||||
notMatchesRanges.push({
|
||||
startLineNumber: group.range.startLineNumber - 1,
|
||||
startColumn: model.getLineMinColumn(group.range.startLineNumber - 1),
|
||||
startColumn: group.range.startColumn,
|
||||
endLineNumber: group.range.endLineNumber,
|
||||
endColumn: model.getLineMaxColumn(group.range.endLineNumber),
|
||||
endColumn: group.range.endColumn
|
||||
});
|
||||
} else {
|
||||
for (const section of group.sections) {
|
||||
if (section.titleRange) {
|
||||
if (!this.containsLine(section.titleRange.startLineNumber, filteredGroup)) {
|
||||
notMatchesRanges.push(this.createCompleteRange(section.titleRange, model));
|
||||
}
|
||||
}
|
||||
for (const setting of section.settings) {
|
||||
if (!this.containsLine(setting.range.startLineNumber, filteredGroup)) {
|
||||
notMatchesRanges.push(this.createCompleteRange(setting.range, model));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return notMatchesRanges;
|
||||
}
|
||||
|
||||
private containsLine(lineNumber: number, settingsGroup: ISettingsGroup): boolean {
|
||||
if (settingsGroup.titleRange && lineNumber >= settingsGroup.titleRange.startLineNumber && lineNumber <= settingsGroup.titleRange.endLineNumber) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const section of settingsGroup.sections) {
|
||||
if (section.titleRange && lineNumber >= section.titleRange.startLineNumber && lineNumber <= section.titleRange.endLineNumber) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const setting of section.settings) {
|
||||
if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private createCompleteRange(range: IRange, model: ITextModel): IRange {
|
||||
return {
|
||||
startLineNumber: range.startLineNumber,
|
||||
startColumn: model.getLineMinColumn(range.startLineNumber),
|
||||
endLineNumber: range.endLineNumber,
|
||||
endColumn: model.getLineMaxColumn(range.endLineNumber)
|
||||
};
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this.decorationIds) {
|
||||
this.decorationIds = this.editor.changeDecorations(changeAccessor => {
|
||||
@@ -859,7 +834,7 @@ interface IIndexedSetting extends ISetting {
|
||||
|
||||
class EditSettingRenderer extends Disposable {
|
||||
|
||||
private editPreferenceWidgetForCusorPosition: EditPreferenceWidget<IIndexedSetting>;
|
||||
private editPreferenceWidgetForCursorPosition: EditPreferenceWidget<IIndexedSetting>;
|
||||
private editPreferenceWidgetForMouseMove: EditPreferenceWidget<IIndexedSetting>;
|
||||
|
||||
private settingsGroups: ISettingsGroup[];
|
||||
@@ -876,11 +851,11 @@ class EditSettingRenderer extends Disposable {
|
||||
) {
|
||||
super();
|
||||
|
||||
this.editPreferenceWidgetForCusorPosition = this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor));
|
||||
this.editPreferenceWidgetForMouseMove = this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor));
|
||||
this.editPreferenceWidgetForCursorPosition = <EditPreferenceWidget<IIndexedSetting>>this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor));
|
||||
this.editPreferenceWidgetForMouseMove = <EditPreferenceWidget<IIndexedSetting>>this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor));
|
||||
this.toggleEditPreferencesForMouseMoveDelayer = new Delayer<void>(75);
|
||||
|
||||
this._register(this.editPreferenceWidgetForCusorPosition.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForCusorPosition, e)));
|
||||
this._register(this.editPreferenceWidgetForCursorPosition.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForCursorPosition, e)));
|
||||
this._register(this.editPreferenceWidgetForMouseMove.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForMouseMove, e)));
|
||||
|
||||
this._register(this.editor.onDidChangeCursorPosition(positionChangeEvent => this.onPositionChanged(positionChangeEvent)));
|
||||
@@ -889,14 +864,14 @@ class EditSettingRenderer extends Disposable {
|
||||
}
|
||||
|
||||
public render(settingsGroups: ISettingsGroup[], associatedPreferencesModel: IPreferencesEditorModel<ISetting>): void {
|
||||
this.editPreferenceWidgetForCusorPosition.hide();
|
||||
this.editPreferenceWidgetForCursorPosition.hide();
|
||||
this.editPreferenceWidgetForMouseMove.hide();
|
||||
this.settingsGroups = settingsGroups;
|
||||
this.associatedPreferencesModel = associatedPreferencesModel;
|
||||
|
||||
const settings = this.getSettings(this.editor.getPosition().lineNumber);
|
||||
if (settings.length) {
|
||||
this.showEditPreferencesWidget(this.editPreferenceWidgetForCusorPosition, settings);
|
||||
this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,7 +881,7 @@ class EditSettingRenderer extends Disposable {
|
||||
|
||||
private onConfigurationChanged(): void {
|
||||
if (!this.editor.getConfiguration().viewInfo.glyphMargin) {
|
||||
this.editPreferenceWidgetForCusorPosition.hide();
|
||||
this.editPreferenceWidgetForCursorPosition.hide();
|
||||
this.editPreferenceWidgetForMouseMove.hide();
|
||||
}
|
||||
}
|
||||
@@ -915,9 +890,9 @@ class EditSettingRenderer extends Disposable {
|
||||
this.editPreferenceWidgetForMouseMove.hide();
|
||||
const settings = this.getSettings(positionChangeEvent.position.lineNumber);
|
||||
if (settings.length) {
|
||||
this.showEditPreferencesWidget(this.editPreferenceWidgetForCusorPosition, settings);
|
||||
this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings);
|
||||
} else {
|
||||
this.editPreferenceWidgetForCusorPosition.hide();
|
||||
this.editPreferenceWidgetForCursorPosition.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,8 +912,8 @@ class EditSettingRenderer extends Disposable {
|
||||
if (this.editPreferenceWidgetForMouseMove.getLine() === line && this.editPreferenceWidgetForMouseMove.isVisible()) {
|
||||
return this.editPreferenceWidgetForMouseMove;
|
||||
}
|
||||
if (this.editPreferenceWidgetForCusorPosition.getLine() === line && this.editPreferenceWidgetForCusorPosition.isVisible()) {
|
||||
return this.editPreferenceWidgetForCusorPosition;
|
||||
if (this.editPreferenceWidgetForCursorPosition.getLine() === line && this.editPreferenceWidgetForCursorPosition.isVisible()) {
|
||||
return this.editPreferenceWidgetForCursorPosition;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -957,7 +932,7 @@ class EditSettingRenderer extends Disposable {
|
||||
const line = settings[0].valueRange.startLineNumber;
|
||||
if (this.editor.getConfiguration().viewInfo.glyphMargin && this.marginFreeFromOtherDecorations(line)) {
|
||||
editPreferencesWidget.show(line, nls.localize('editTtile', "Edit"), settings);
|
||||
const editPreferenceWidgetToHide = editPreferencesWidget === this.editPreferenceWidgetForCusorPosition ? this.editPreferenceWidgetForMouseMove : this.editPreferenceWidgetForCusorPosition;
|
||||
const editPreferenceWidgetToHide = editPreferencesWidget === this.editPreferenceWidgetForCursorPosition ? this.editPreferenceWidgetForMouseMove : this.editPreferenceWidgetForCursorPosition;
|
||||
editPreferenceWidgetToHide.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import * as DOM from 'vs/base/browser/dom';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
@@ -22,7 +21,7 @@ import { ISettingsGroup } from 'vs/workbench/parts/preferences/common/preference
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { attachInputBoxStyler, attachStylerCallback, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
@@ -32,7 +31,6 @@ import { Separator, ActionBar, ActionsOrientation, BaseActionItem } from 'vs/bas
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { render as renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
|
||||
import { PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_INACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
|
||||
@@ -103,32 +101,20 @@ export class SettingsHeaderWidget extends Widget implements IViewZone {
|
||||
|
||||
export class DefaultSettingsHeaderWidget extends SettingsHeaderWidget {
|
||||
|
||||
private linkElement: HTMLElement;
|
||||
private _onClick = this._register(new Emitter<void>());
|
||||
public onClick: Event<void> = this._onClick.event;
|
||||
|
||||
protected create() {
|
||||
super.create();
|
||||
|
||||
this.linkElement = DOM.append(this.titleContainer, DOM.$('a.settings-header-natural-language-link'));
|
||||
this.linkElement.textContent = localize('defaultSettingsFuzzyPrompt', "Try natural language search!");
|
||||
|
||||
this.onclick(this.linkElement, e => this._onClick.fire());
|
||||
this.toggleMessage(true);
|
||||
}
|
||||
|
||||
public toggleMessage(hasSettings: boolean, promptFuzzy = false): void {
|
||||
public toggleMessage(hasSettings: boolean): void {
|
||||
if (hasSettings) {
|
||||
this.setMessage(localize('defaultSettings', "Place your settings in the right hand side editor to override."));
|
||||
DOM.addClass(this.linkElement, 'hidden');
|
||||
} else {
|
||||
this.setMessage(localize('noSettingsFound', "No Settings Found."));
|
||||
|
||||
if (promptFuzzy) {
|
||||
DOM.removeClass(this.linkElement, 'hidden');
|
||||
} else {
|
||||
DOM.addClass(this.linkElement, 'hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -533,7 +519,6 @@ export class SettingsTargetsWidget extends Widget {
|
||||
|
||||
export interface SearchOptions extends IInputOptions {
|
||||
focusKey?: IContextKey<boolean>;
|
||||
showFuzzyToggle?: boolean;
|
||||
showResultCount?: boolean;
|
||||
}
|
||||
|
||||
@@ -544,7 +529,6 @@ export class SearchWidget extends Widget {
|
||||
private countElement: HTMLElement;
|
||||
private searchContainer: HTMLElement;
|
||||
private inputBox: InputBox;
|
||||
private fuzzyToggle: Checkbox;
|
||||
private controlsDiv: HTMLElement;
|
||||
|
||||
private _onDidChange: Emitter<string> = this._register(new Emitter<string>());
|
||||
@@ -562,32 +546,10 @@ export class SearchWidget extends Widget {
|
||||
this.create(parent);
|
||||
}
|
||||
|
||||
public get fuzzyEnabled(): boolean {
|
||||
return this.fuzzyToggle.checked && this.fuzzyToggle.enabled;
|
||||
}
|
||||
|
||||
public set fuzzyEnabled(value: boolean) {
|
||||
this.fuzzyToggle.checked = value;
|
||||
}
|
||||
|
||||
private create(parent: HTMLElement) {
|
||||
this.domNode = DOM.append(parent, DOM.$('div.settings-header-widget'));
|
||||
this.createSearchContainer(DOM.append(this.domNode, DOM.$('div.settings-search-container')));
|
||||
this.controlsDiv = DOM.append(this.domNode, DOM.$('div.settings-search-controls'));
|
||||
if (this.options.showFuzzyToggle) {
|
||||
this.fuzzyToggle = this._register(new Checkbox({
|
||||
actionClassName: 'prefs-natural-language-search-toggle',
|
||||
isChecked: false,
|
||||
onChange: () => {
|
||||
this.inputBox.focus();
|
||||
this._onDidChange.fire();
|
||||
},
|
||||
title: localize('enableFuzzySearch', 'Enable natural language search')
|
||||
}));
|
||||
this.fuzzyToggle.domNode.innerHTML = renderOcticons('$(light-bulb)');
|
||||
DOM.append(this.controlsDiv, this.fuzzyToggle.domNode);
|
||||
this._register(attachCheckboxStyler(this.fuzzyToggle, this.themeService));
|
||||
}
|
||||
|
||||
if (this.options.showResultCount) {
|
||||
this.countElement = DOM.append(this.controlsDiv, DOM.$('.settings-count-widget'));
|
||||
@@ -639,16 +601,6 @@ export class SearchWidget extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
public setFuzzyToggleVisible(visible: boolean): void {
|
||||
if (visible) {
|
||||
this.fuzzyToggle.domNode.classList.remove('hidden');
|
||||
this.fuzzyToggle.enable();
|
||||
} else {
|
||||
this.fuzzyToggle.domNode.classList.add('hidden');
|
||||
this.fuzzyToggle.disable();
|
||||
}
|
||||
}
|
||||
|
||||
private styleCountElementForeground() {
|
||||
const colorId = DOM.hasClass(this.countElement, 'no-results') ? errorForeground : badgeForeground;
|
||||
const color = this.themeService.getTheme().getColor(colorId);
|
||||
@@ -673,8 +625,7 @@ export class SearchWidget extends Widget {
|
||||
|
||||
private getControlsWidth(): number {
|
||||
const countWidth = this.countElement ? DOM.getTotalWidth(this.countElement) : 0;
|
||||
const fuzzyToggleWidth = this.fuzzyToggle ? DOM.getTotalWidth(this.fuzzyToggle.domNode) : 0;
|
||||
return countWidth + fuzzyToggleWidth + 20;
|
||||
return countWidth + 20;
|
||||
}
|
||||
|
||||
public focus() {
|
||||
|
||||
@@ -56,15 +56,31 @@ export interface ISetting {
|
||||
overrideOf?: ISetting;
|
||||
}
|
||||
|
||||
export interface ISearchResult {
|
||||
filterMatches: ISettingMatch[];
|
||||
metadata?: IFilterMetadata;
|
||||
}
|
||||
|
||||
export interface ISearchResultGroup {
|
||||
id: string;
|
||||
label: string;
|
||||
result: ISearchResult;
|
||||
}
|
||||
|
||||
export interface IFilterResult {
|
||||
query: string;
|
||||
query?: string;
|
||||
filteredGroups: ISettingsGroup[];
|
||||
allGroups: ISettingsGroup[];
|
||||
matches: IRange[];
|
||||
fuzzySearchAvailable?: boolean;
|
||||
metadata?: IFilterMetadata;
|
||||
}
|
||||
|
||||
export interface ISettingMatch {
|
||||
setting: ISetting;
|
||||
matches: IRange[];
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface IScoredResults {
|
||||
[key: string]: number;
|
||||
}
|
||||
@@ -86,14 +102,14 @@ export interface IPreferencesEditorModel<T> {
|
||||
}
|
||||
|
||||
export type IGroupFilter = (group: ISettingsGroup) => boolean;
|
||||
export type ISettingMatcher = (setting: ISetting) => IRange[];
|
||||
export type ISettingMatcher = (setting: ISetting) => { matches: IRange[], score: number };
|
||||
|
||||
export interface ISettingsEditorModel extends IPreferencesEditorModel<ISetting> {
|
||||
readonly onDidChangeGroups: Event<void>;
|
||||
settingsGroups: ISettingsGroup[];
|
||||
groupsTerms: string[];
|
||||
filterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher, mostRelevantSettings?: string[]): IFilterResult;
|
||||
filterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher): ISettingMatch[];
|
||||
findValueMatches(filter: string, setting: ISetting): IRange[];
|
||||
updateResultGroup(id: string, resultGroup: ISearchResultGroup): IFilterResult;
|
||||
}
|
||||
|
||||
export interface IKeybindingsEditorModel<T> extends IPreferencesEditorModel<T> {
|
||||
@@ -159,15 +175,14 @@ export const IPreferencesSearchService = createDecorator<IPreferencesSearchServi
|
||||
export interface IPreferencesSearchService {
|
||||
_serviceBrand: any;
|
||||
|
||||
remoteSearchAllowed: boolean;
|
||||
endpoint: IEndpointDetails;
|
||||
onRemoteSearchEnablementChanged: Event<boolean>;
|
||||
|
||||
startSearch(filter: string, remote: boolean): IPreferencesSearchModel;
|
||||
getLocalSearchProvider(filter: string): ISearchProvider;
|
||||
getRemoteSearchProvider(filter: string): ISearchProvider;
|
||||
}
|
||||
|
||||
export interface IPreferencesSearchModel {
|
||||
filterPreferences(preferencesModel: ISettingsEditorModel): TPromise<IFilterResult>;
|
||||
export interface ISearchProvider {
|
||||
searchModel(preferencesModel: ISettingsEditorModel): TPromise<ISearchResult>;
|
||||
}
|
||||
|
||||
export const CONTEXT_SETTINGS_EDITOR = new RawContextKey<boolean>('inSettingsEditor', false);
|
||||
|
||||
@@ -5,93 +5,72 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { tail } from 'vs/base/common/arrays';
|
||||
import * as map from 'vs/base/common/map';
|
||||
import { tail, flatten, first } from 'vs/base/common/arrays';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IReference, Disposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { visit, JSONVisitor } from 'vs/base/common/json';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ITextModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||
import { EditorModel } from 'vs/workbench/common/editor';
|
||||
import { IConfigurationNode, IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN, IConfigurationPropertySchema, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { ISettingsEditorModel, IKeybindingsEditorModel, ISettingsGroup, ISetting, IFilterResult, ISettingsSection, IGroupFilter, ISettingMatcher } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { ISettingsEditorModel, IKeybindingsEditorModel, ISettingsGroup, ISetting, IFilterResult, IGroupFilter, ISettingMatcher, ISettingMatch, ISearchResultGroup } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
|
||||
export abstract class AbstractSettingsModel extends EditorModel {
|
||||
|
||||
public get groupsTerms(): string[] {
|
||||
return this.settingsGroups.map(group => '@' + group.id);
|
||||
protected _currentResultGroups = new Map<string, ISearchResultGroup>();
|
||||
|
||||
public updateResultGroup(id: string, resultGroup: ISearchResultGroup): IFilterResult {
|
||||
if (resultGroup) {
|
||||
this._currentResultGroups.set(id, resultGroup);
|
||||
} else {
|
||||
this._currentResultGroups.delete(id);
|
||||
}
|
||||
|
||||
this.removeDuplicateResults();
|
||||
return this.update();
|
||||
}
|
||||
|
||||
protected doFilterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher): IFilterResult {
|
||||
const allGroups = this.settingsGroups;
|
||||
/**
|
||||
* Remove duplicates between result groups, preferring results in earlier groups
|
||||
*/
|
||||
private removeDuplicateResults(): void {
|
||||
// Depends on order of map keys
|
||||
const settingKeys = new Set<string>();
|
||||
this._currentResultGroups.forEach((group, id) => {
|
||||
group.result.filterMatches = group.result.filterMatches.filter(s => !settingKeys.has(s.setting.key));
|
||||
group.result.filterMatches.forEach(s => settingKeys.add(s.setting.key));
|
||||
});
|
||||
}
|
||||
|
||||
if (!filter) {
|
||||
return {
|
||||
filteredGroups: allGroups,
|
||||
allGroups,
|
||||
matches: [],
|
||||
query: filter
|
||||
};
|
||||
}
|
||||
public filterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher): ISettingMatch[] {
|
||||
const allGroups = this.filterGroups;
|
||||
|
||||
const group = this.filterByGroupTerm(filter);
|
||||
if (group) {
|
||||
return {
|
||||
filteredGroups: [group],
|
||||
allGroups,
|
||||
matches: [],
|
||||
query: filter
|
||||
};
|
||||
}
|
||||
|
||||
const matches: IRange[] = [];
|
||||
const filteredGroups: ISettingsGroup[] = [];
|
||||
const filterMatches: ISettingMatch[] = [];
|
||||
for (const group of allGroups) {
|
||||
const groupMatched = groupFilter(group);
|
||||
const sections: ISettingsSection[] = [];
|
||||
for (const section of group.sections) {
|
||||
const settings: ISetting[] = [];
|
||||
for (const setting of section.settings) {
|
||||
const settingMatches = settingMatcher(setting);
|
||||
if (groupMatched || settingMatches && settingMatches.length) {
|
||||
settings.push(setting);
|
||||
}
|
||||
const settingMatchResult = settingMatcher(setting);
|
||||
|
||||
if (settingMatches) {
|
||||
matches.push(...settingMatches);
|
||||
if (groupMatched || settingMatchResult) {
|
||||
filterMatches.push({
|
||||
setting,
|
||||
matches: settingMatchResult && settingMatchResult.matches,
|
||||
score: settingMatchResult ? settingMatchResult.score : 0
|
||||
});
|
||||
}
|
||||
}
|
||||
if (settings.length) {
|
||||
sections.push({
|
||||
title: section.title,
|
||||
settings,
|
||||
titleRange: section.titleRange
|
||||
});
|
||||
}
|
||||
}
|
||||
if (sections.length) {
|
||||
filteredGroups.push({
|
||||
id: group.id,
|
||||
title: group.title,
|
||||
titleRange: group.titleRange,
|
||||
sections,
|
||||
range: group.range
|
||||
});
|
||||
}
|
||||
}
|
||||
return { filteredGroups, matches, allGroups, query: filter };
|
||||
}
|
||||
|
||||
private filterByGroupTerm(filter: string): ISettingsGroup {
|
||||
if (this.groupsTerms.indexOf(filter) !== -1) {
|
||||
const id = filter.substring(1);
|
||||
return this.settingsGroups.filter(group => group.id === id)[0];
|
||||
}
|
||||
return null;
|
||||
return filterMatches.sort((a, b) => b.score - a.score);
|
||||
}
|
||||
|
||||
public getPreference(key: string): ISetting {
|
||||
@@ -107,9 +86,15 @@ export abstract class AbstractSettingsModel extends EditorModel {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected get filterGroups(): ISettingsGroup[] {
|
||||
return this.settingsGroups;
|
||||
}
|
||||
|
||||
public abstract settingsGroups: ISettingsGroup[];
|
||||
|
||||
public abstract findValueMatches(filter: string, setting: ISetting): IRange[];
|
||||
|
||||
protected abstract update(): IFilterResult;
|
||||
}
|
||||
|
||||
export class SettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
|
||||
@@ -149,10 +134,6 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti
|
||||
return this.settingsModel.getValue();
|
||||
}
|
||||
|
||||
public filterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher): IFilterResult {
|
||||
return this.doFilterSettings(filter, groupFilter, settingMatcher);
|
||||
}
|
||||
|
||||
public findValueMatches(filter: string, setting: ISetting): IRange[] {
|
||||
return this.settingsModel.findMatches(filter, setting.valueRange, false, false, null, false).map(match => match.range);
|
||||
}
|
||||
@@ -164,6 +145,43 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti
|
||||
protected parse(): void {
|
||||
this._settingsGroups = parse(this.settingsModel, (property: string, previousParents: string[]): boolean => this.isSettingsProperty(property, previousParents));
|
||||
}
|
||||
|
||||
protected update(): IFilterResult {
|
||||
const resultGroups = map.values(this._currentResultGroups);
|
||||
if (!resultGroups.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Transform resultGroups into IFilterResult - ISetting ranges are already correct here
|
||||
const filteredSettings: ISetting[] = [];
|
||||
const matches: IRange[] = [];
|
||||
resultGroups.forEach(group => {
|
||||
group.result.filterMatches.forEach(filterMatch => {
|
||||
filteredSettings.push(filterMatch.setting);
|
||||
matches.push(...filterMatch.matches);
|
||||
});
|
||||
});
|
||||
|
||||
let filteredGroup: ISettingsGroup;
|
||||
const modelGroup = this.settingsGroups[0]; // Editable model has one or zero groups
|
||||
if (modelGroup) {
|
||||
filteredGroup = {
|
||||
id: modelGroup.id,
|
||||
range: modelGroup.range,
|
||||
sections: [{
|
||||
settings: filteredSettings
|
||||
}],
|
||||
title: modelGroup.title,
|
||||
titleRange: modelGroup.titleRange
|
||||
};
|
||||
}
|
||||
|
||||
return <IFilterResult>{
|
||||
allGroups: this.settingsGroups,
|
||||
filteredGroups: filteredGroup ? [filteredGroup] : [],
|
||||
matches
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, previousParents: string[]) => boolean): ISettingsGroup[] {
|
||||
@@ -382,6 +400,7 @@ export class DefaultSettings extends Disposable {
|
||||
if (!this._allSettingsGroups) {
|
||||
this.parse();
|
||||
}
|
||||
|
||||
return this._allSettingsGroups;
|
||||
}
|
||||
|
||||
@@ -390,7 +409,7 @@ export class DefaultSettings extends Disposable {
|
||||
this.initAllSettingsMap(settingsGroups);
|
||||
const mostCommonlyUsed = this.getMostCommonlyUsedSettings(settingsGroups);
|
||||
this._allSettingsGroups = [mostCommonlyUsed, ...settingsGroups];
|
||||
this._content = this.toContent(true, [mostCommonlyUsed], settingsGroups);
|
||||
this._content = this.toContent(true, this._allSettingsGroups);
|
||||
return this._content;
|
||||
}
|
||||
|
||||
@@ -538,17 +557,14 @@ export class DefaultSettings extends Disposable {
|
||||
return c1.order - c2.order;
|
||||
}
|
||||
|
||||
private toContent(asArray: boolean, ...settingsGroups: ISettingsGroup[][]): string {
|
||||
private toContent(asArray: boolean, settingsGroups: ISettingsGroup[]): string {
|
||||
const builder = new SettingsContentBuilder();
|
||||
if (asArray) {
|
||||
builder.pushLine('[');
|
||||
}
|
||||
settingsGroups.forEach((settingsGroup, i) => {
|
||||
builder.pushGroups(settingsGroup);
|
||||
|
||||
if (i !== settingsGroups.length - 1) {
|
||||
builder.pushLine(',');
|
||||
}
|
||||
builder.pushGroup(settingsGroup);
|
||||
builder.pushLine(',');
|
||||
});
|
||||
if (asArray) {
|
||||
builder.pushLine(']');
|
||||
@@ -586,49 +602,101 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
|
||||
return this.defaultSettings.settingsGroups;
|
||||
}
|
||||
|
||||
public filterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher, mostRelevantSettings?: string[]): IFilterResult {
|
||||
if (mostRelevantSettings) {
|
||||
const mostRelevantGroup = this.renderMostRelevantSettings(mostRelevantSettings);
|
||||
|
||||
// calculate match ranges
|
||||
const matches = mostRelevantGroup.sections[0].settings.reduce((prev, s) => {
|
||||
return prev.concat(settingMatcher(s));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
allGroups: [...this.settingsGroups, mostRelevantGroup],
|
||||
filteredGroups: mostRelevantGroup.sections[0].settings.length ? [mostRelevantGroup] : [],
|
||||
matches,
|
||||
query: filter
|
||||
};
|
||||
} else {
|
||||
// Do local search and add empty 'most relevant' group
|
||||
const mostRelevantGroup = this.renderMostRelevantSettings([]);
|
||||
const result = this.doFilterSettings(filter, groupFilter, settingMatcher);
|
||||
result.allGroups = [...result.allGroups, mostRelevantGroup];
|
||||
return result;
|
||||
}
|
||||
protected get filterGroups(): ISettingsGroup[] {
|
||||
// Don't look at "commonly used" for filter
|
||||
return this.settingsGroups.slice(1);
|
||||
}
|
||||
|
||||
private renderMostRelevantSettings(mostRelevantSettings: string[]): ISettingsGroup {
|
||||
const mostRelevantLineOffset = tail(this.settingsGroups).range.endLineNumber + 2;
|
||||
const builder = new SettingsContentBuilder(mostRelevantLineOffset - 1);
|
||||
protected update(): IFilterResult {
|
||||
// Grab current result groups, only render non-empty groups
|
||||
const resultGroups = map.values(this._currentResultGroups);
|
||||
const nonEmptyResultGroups = resultGroups.filter(group => group.result.filterMatches.length);
|
||||
|
||||
const startLine = tail(this.settingsGroups).range.endLineNumber + 2;
|
||||
const { settingsGroups: filteredGroups, matches } = this.writeResultGroups(nonEmptyResultGroups, startLine);
|
||||
|
||||
const groupWithMetadata = first(resultGroups, group => !!group.result.metadata);
|
||||
return resultGroups.length ?
|
||||
<IFilterResult>{
|
||||
allGroups: this.settingsGroups,
|
||||
filteredGroups,
|
||||
matches,
|
||||
metadata: groupWithMetadata && groupWithMetadata.result.metadata
|
||||
} :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the ISearchResultGroups to text, and write it to the editor model
|
||||
*/
|
||||
private writeResultGroups(groups: ISearchResultGroup[], startLine: number): { matches: IRange[], settingsGroups: ISettingsGroup[] } {
|
||||
const contentBuilderOffset = startLine - 1;
|
||||
const builder = new SettingsContentBuilder(contentBuilderOffset);
|
||||
|
||||
const settingsGroups: ISettingsGroup[] = [];
|
||||
const matches: IRange[] = [];
|
||||
builder.pushLine(',');
|
||||
const mostRelevantGroup = this.getMostRelevantSettings(mostRelevantSettings);
|
||||
builder.pushGroups([mostRelevantGroup]);
|
||||
builder.pushLine('');
|
||||
groups.forEach(resultGroup => {
|
||||
const settingsGroup = this.getGroup(resultGroup);
|
||||
settingsGroups.push(settingsGroup);
|
||||
matches.push(...this.writeSettingsGroupToBuilder(builder, settingsGroup, resultGroup.result.filterMatches));
|
||||
});
|
||||
|
||||
// note: 1-indexed line numbers here
|
||||
const mostRelevantContent = builder.getContent();
|
||||
const mostRelevantEndLine = this._model.getLineCount();
|
||||
this._model.applyEdits([
|
||||
{
|
||||
text: mostRelevantContent,
|
||||
range: new Range(mostRelevantLineOffset, 1, mostRelevantEndLine, 1)
|
||||
}
|
||||
]);
|
||||
const groupContent = builder.getContent() + '\n';
|
||||
const groupEndLine = this._model.getLineCount();
|
||||
const cursorPosition = new Selection(startLine, 1, startLine, 1);
|
||||
const edit: IIdentifiedSingleEditOperation = {
|
||||
text: groupContent,
|
||||
forceMoveMarkers: true,
|
||||
range: new Range(startLine, 1, groupEndLine, 1),
|
||||
identifier: { major: 1, minor: 0 }
|
||||
};
|
||||
|
||||
return mostRelevantGroup;
|
||||
this._model.pushEditOperations([cursorPosition], [edit], () => [cursorPosition]);
|
||||
|
||||
// Force tokenization now - otherwise it may be slightly delayed, causing a flash of white text
|
||||
const tokenizeTo = Math.min(startLine + 60, this._model.getLineCount());
|
||||
this._model.forceTokenization(tokenizeTo);
|
||||
|
||||
return { matches, settingsGroups };
|
||||
}
|
||||
|
||||
private writeSettingsGroupToBuilder(builder: SettingsContentBuilder, settingsGroup: ISettingsGroup, filterMatches: ISettingMatch[]): IRange[] {
|
||||
// Fix match ranges to offset from setting start line
|
||||
filterMatches = filterMatches.map(filteredMatch => {
|
||||
return <ISettingMatch>{
|
||||
setting: filteredMatch.setting,
|
||||
score: filteredMatch.score,
|
||||
matches: filteredMatch.matches && filteredMatch.matches.map(match => {
|
||||
return new Range(
|
||||
match.startLineNumber - filteredMatch.setting.range.startLineNumber,
|
||||
match.startColumn,
|
||||
match.endLineNumber - filteredMatch.setting.range.startLineNumber,
|
||||
match.endColumn);
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
builder.pushGroup(settingsGroup);
|
||||
builder.pushLine(',');
|
||||
|
||||
// builder has rewritten settings ranges, fix match ranges
|
||||
const fixedMatches = flatten(
|
||||
filterMatches
|
||||
.map(m => m.matches || [])
|
||||
.map((settingMatches, i) => {
|
||||
const setting = settingsGroup.sections[0].settings[i];
|
||||
return settingMatches.map(range => {
|
||||
return new Range(
|
||||
range.startLineNumber + setting.range.startLineNumber,
|
||||
range.startColumn,
|
||||
range.endLineNumber + setting.range.startLineNumber,
|
||||
range.endColumn);
|
||||
});
|
||||
}));
|
||||
|
||||
return fixedMatches;
|
||||
}
|
||||
|
||||
public findValueMatches(filter: string, setting: ISetting): IRange[] {
|
||||
@@ -648,30 +716,28 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
|
||||
return null;
|
||||
}
|
||||
|
||||
private getMostRelevantSettings(rankedSettingNames: string[]): ISettingsGroup {
|
||||
const settings = rankedSettingNames.map(key => {
|
||||
const setting = this.defaultSettings.getSettingByName(key);
|
||||
if (setting) {
|
||||
return <ISetting>{
|
||||
description: setting.description,
|
||||
key: setting.key,
|
||||
value: setting.value,
|
||||
range: null,
|
||||
valueRange: null,
|
||||
overrides: []
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}).filter(setting => !!setting);
|
||||
private copySettings(settings: ISetting[]): ISetting[] {
|
||||
return settings.map(setting => {
|
||||
return <ISetting>{
|
||||
description: setting.description,
|
||||
key: setting.key,
|
||||
value: setting.value,
|
||||
range: null,
|
||||
valueRange: null,
|
||||
overrides: []
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getGroup(resultGroup: ISearchResultGroup): ISettingsGroup {
|
||||
return <ISettingsGroup>{
|
||||
id: 'mostRelevant',
|
||||
id: resultGroup.id,
|
||||
range: null,
|
||||
title: nls.localize('mostRelevant', "Most Relevant"),
|
||||
title: resultGroup.label,
|
||||
titleRange: null,
|
||||
sections: [
|
||||
{
|
||||
settings
|
||||
settings: this.copySettings(resultGroup.result.filterMatches.map(m => m.setting))
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -681,10 +747,6 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
|
||||
class SettingsContentBuilder {
|
||||
private _contentByLines: string[];
|
||||
|
||||
get lines(): string[] {
|
||||
return this._contentByLines;
|
||||
}
|
||||
|
||||
private get lineCountWithOffset(): number {
|
||||
return this._contentByLines.length + this._rangeOffset;
|
||||
}
|
||||
@@ -705,24 +767,23 @@ class SettingsContentBuilder {
|
||||
this._contentByLines.push(...lineText);
|
||||
}
|
||||
|
||||
pushGroups(settingsGroups: ISettingsGroup[]): void {
|
||||
let lastSetting: ISetting = null;
|
||||
pushGroup(settingsGroups: ISettingsGroup): void {
|
||||
this._contentByLines.push('{');
|
||||
this._contentByLines.push('');
|
||||
for (const group of settingsGroups) {
|
||||
this._contentByLines.push('');
|
||||
lastSetting = this.pushGroup(group);
|
||||
}
|
||||
this._contentByLines.push('');
|
||||
const lastSetting = this._pushGroup(settingsGroups);
|
||||
|
||||
if (lastSetting) {
|
||||
// Strip the comma from the last setting
|
||||
const lineIdx = this.offsetIndexToIndex(lastSetting.range.endLineNumber);
|
||||
const content = this._contentByLines[lineIdx - 2];
|
||||
this._contentByLines[lineIdx - 2] = content.substring(0, content.length - 1);
|
||||
}
|
||||
|
||||
this._contentByLines.push('}');
|
||||
}
|
||||
|
||||
private pushGroup(group: ISettingsGroup): ISetting {
|
||||
private _pushGroup(group: ISettingsGroup): ISetting {
|
||||
const indent = ' ';
|
||||
let lastSetting: ISetting = null;
|
||||
let groupStart = this.lineCountWithOffset + 1;
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ISettingsEditorModel, IFilterResult, ISetting, ISettingsGroup, IWorkbenchSettingsConfiguration, IFilterMetadata, IPreferencesSearchService, IPreferencesSearchModel } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { ISettingsEditorModel, ISetting, ISettingsGroup, IWorkbenchSettingsConfiguration, IFilterMetadata, IPreferencesSearchService, ISearchResult, ISearchProvider, IGroupFilter, ISettingMatcher, IScoredResults } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { distinct, top } from 'vs/base/common/arrays';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
@@ -20,7 +18,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { asJson } from 'vs/base/node/request';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export interface IEndpointDetails {
|
||||
urlBase: string;
|
||||
@@ -30,19 +27,15 @@ export interface IEndpointDetails {
|
||||
export class PreferencesSearchService extends Disposable implements IPreferencesSearchService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _onRemoteSearchEnablementChanged = new Emitter<boolean>();
|
||||
public onRemoteSearchEnablementChanged: Event<boolean> = this._onRemoteSearchEnablementChanged.event;
|
||||
|
||||
constructor(
|
||||
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
this._register(configurationService.onDidChangeConfiguration(() => this._onRemoteSearchEnablementChanged.fire(this.remoteSearchAllowed)));
|
||||
}
|
||||
|
||||
get remoteSearchAllowed(): boolean {
|
||||
private get remoteSearchAllowed(): boolean {
|
||||
if (this.environmentService.appQuality === 'stable') {
|
||||
return false;
|
||||
}
|
||||
@@ -69,76 +62,60 @@ export class PreferencesSearchService extends Disposable implements IPreferences
|
||||
}
|
||||
}
|
||||
|
||||
startSearch(filter: string, remote: boolean): PreferencesSearchModel {
|
||||
return this.instantiationService.createInstance(PreferencesSearchModel, this, filter, remote);
|
||||
getRemoteSearchProvider(filter: string): RemoteSearchProvider {
|
||||
return this.remoteSearchAllowed && this.instantiationService.createInstance(RemoteSearchProvider, filter, this.endpoint);
|
||||
}
|
||||
|
||||
getLocalSearchProvider(filter: string): LocalSearchProvider {
|
||||
return this.instantiationService.createInstance(LocalSearchProvider, filter);
|
||||
}
|
||||
}
|
||||
|
||||
export class PreferencesSearchModel implements IPreferencesSearchModel {
|
||||
private _localProvider: LocalSearchProvider;
|
||||
private _remoteProvider: RemoteSearchProvider;
|
||||
|
||||
constructor(
|
||||
private provider: IPreferencesSearchService, private filter: string, remote: boolean,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService
|
||||
) {
|
||||
this._localProvider = new LocalSearchProvider(filter);
|
||||
|
||||
if (remote && filter) {
|
||||
this._remoteProvider = instantiationService.createInstance(RemoteSearchProvider, filter, this.provider.endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
filterPreferences(preferencesModel: ISettingsEditorModel): TPromise<IFilterResult> {
|
||||
if (!this.filter) {
|
||||
return TPromise.wrap(null);
|
||||
}
|
||||
|
||||
if (this._remoteProvider) {
|
||||
return this._remoteProvider.filterPreferences(preferencesModel).then(null, err => {
|
||||
const message = errors.getErrorMessage(err);
|
||||
|
||||
if (message.toLowerCase() !== 'canceled') {
|
||||
/* __GDPR__
|
||||
"defaultSettings.searchError" : {
|
||||
"message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('defaultSettings.searchError', { message });
|
||||
}
|
||||
|
||||
return this._localProvider.filterPreferences(preferencesModel);
|
||||
});
|
||||
} else {
|
||||
return this._localProvider.filterPreferences(preferencesModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LocalSearchProvider {
|
||||
export class LocalSearchProvider implements ISearchProvider {
|
||||
private _filter: string;
|
||||
|
||||
constructor(filter: string) {
|
||||
this._filter = filter;
|
||||
}
|
||||
|
||||
filterPreferences(preferencesModel: ISettingsEditorModel): TPromise<IFilterResult> {
|
||||
const regex = strings.createRegExp(this._filter, false, { global: true });
|
||||
|
||||
const groupFilter = (group: ISettingsGroup) => {
|
||||
return regex.test(group.title);
|
||||
};
|
||||
searchModel(preferencesModel: ISettingsEditorModel): TPromise<ISearchResult> {
|
||||
if (!this._filter) {
|
||||
return TPromise.wrap(null);
|
||||
}
|
||||
|
||||
let score = 1000; // Sort is not stable
|
||||
const settingMatcher = (setting: ISetting) => {
|
||||
return new SettingMatches(this._filter, setting, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches;
|
||||
const matches = new SettingMatches(this._filter, setting, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches;
|
||||
return matches && matches.length ?
|
||||
{
|
||||
matches,
|
||||
score: score--
|
||||
} :
|
||||
null;
|
||||
};
|
||||
|
||||
return TPromise.wrap(preferencesModel.filterSettings(this._filter, groupFilter, settingMatcher));
|
||||
const filterMatches = preferencesModel.filterSettings(this._filter, this.getGroupFilter(this._filter), settingMatcher);
|
||||
return TPromise.wrap({
|
||||
filterMatches
|
||||
});
|
||||
}
|
||||
|
||||
private getGroupFilter(filter: string): IGroupFilter {
|
||||
if (strings.startsWith(filter, '@')) {
|
||||
const groupId = filter.replace(/^@/, '');
|
||||
return (group: ISettingsGroup) => {
|
||||
return group.id.toLowerCase() === groupId.toLowerCase();
|
||||
};
|
||||
} else {
|
||||
const regex = strings.createRegExp(this._filter, false, { global: true });
|
||||
return (group: ISettingsGroup) => {
|
||||
return regex.test(group.title);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteSearchProvider {
|
||||
export class RemoteSearchProvider implements ISearchProvider {
|
||||
private _filter: string;
|
||||
private _remoteSearchP: TPromise<IFilterMetadata>;
|
||||
|
||||
@@ -147,23 +124,26 @@ class RemoteSearchProvider {
|
||||
@IRequestService private requestService: IRequestService
|
||||
) {
|
||||
this._filter = filter;
|
||||
this._remoteSearchP = filter ? this.getSettingsFromBing(filter, endpoint) : TPromise.wrap(null);
|
||||
|
||||
// @queries are always handled by local filter
|
||||
this._remoteSearchP = filter && !strings.startsWith(filter, '@') ?
|
||||
this.getSettingsFromBing(filter, endpoint) :
|
||||
TPromise.wrap(null);
|
||||
}
|
||||
|
||||
filterPreferences(preferencesModel: ISettingsEditorModel): TPromise<IFilterResult> {
|
||||
searchModel(preferencesModel: ISettingsEditorModel): TPromise<ISearchResult> {
|
||||
return this._remoteSearchP.then(remoteResult => {
|
||||
if (remoteResult) {
|
||||
let sortedNames = Object.keys(remoteResult.scoredResults).sort((a, b) => remoteResult.scoredResults[b] - remoteResult.scoredResults[a]);
|
||||
if (sortedNames.length) {
|
||||
const highScore = remoteResult.scoredResults[sortedNames[0]];
|
||||
const minScore = highScore / 5;
|
||||
sortedNames = sortedNames.filter(name => remoteResult.scoredResults[name] >= minScore);
|
||||
}
|
||||
const highScoreKey = top(Object.keys(remoteResult.scoredResults), (a, b) => remoteResult.scoredResults[b] - remoteResult.scoredResults[a], 1)[0];
|
||||
const highScore = highScoreKey ? remoteResult.scoredResults[highScoreKey] : 0;
|
||||
const minScore = highScore / 5;
|
||||
|
||||
const settingMatcher = this.getRemoteSettingMatcher(sortedNames, preferencesModel);
|
||||
const result = preferencesModel.filterSettings(this._filter, group => null, settingMatcher, sortedNames);
|
||||
result.metadata = remoteResult;
|
||||
return result;
|
||||
const settingMatcher = this.getRemoteSettingMatcher(remoteResult.scoredResults, minScore, preferencesModel);
|
||||
const filterMatches = preferencesModel.filterSettings(this._filter, group => null, settingMatcher);
|
||||
return <ISearchResult>{
|
||||
filterMatches,
|
||||
metadata: remoteResult
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -218,19 +198,15 @@ class RemoteSearchProvider {
|
||||
return TPromise.as(p as any);
|
||||
}
|
||||
|
||||
private getRemoteSettingMatcher(names: string[], preferencesModel: ISettingsEditorModel): any {
|
||||
const resultSet = new Set();
|
||||
names.forEach(name => resultSet.add(name));
|
||||
|
||||
private getRemoteSettingMatcher(scoredResults: IScoredResults, minScore: number, preferencesModel: ISettingsEditorModel): ISettingMatcher {
|
||||
return (setting: ISetting) => {
|
||||
if (resultSet.has(setting.key)) {
|
||||
const score = scoredResults[setting.key];
|
||||
if (typeof score === 'number' && score >= minScore) {
|
||||
const settingMatches = new SettingMatches(this._filter, setting, false, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches;
|
||||
if (settingMatches.length) {
|
||||
return settingMatches;
|
||||
}
|
||||
return { matches: settingMatches, score: scoredResults[setting.key] };
|
||||
}
|
||||
|
||||
return [];
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user