Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src/vs
diff options
context:
space:
mode:
authorRob Lourens <roblourens@gmail.com>2017-10-24 04:41:41 +0300
committerGitHub <noreply@github.com>2017-10-24 04:41:41 +0300
commitaaec2980609a4bbe44d0cfe2d3f78c85cba1be83 (patch)
tree5ecdcbfbb61bea93f53374fb6ec19efec6032570 /src/vs
parent046417ee42278ba62fa30266b9a87244130e3252 (diff)
parent030ef525c71fd63a51564d6fb4719ab04b460110 (diff)
Merge pull request #35679 from Microsoft/roblou/settingsSearch
WIP - settings search
Diffstat (limited to 'src/vs')
-rw-r--r--src/vs/base/browser/ui/checkbox/checkbox.ts4
-rw-r--r--src/vs/workbench/electron-browser/main.contribution.ts10
-rw-r--r--src/vs/workbench/parts/preferences/browser/media/preferences.css41
-rw-r--r--src/vs/workbench/parts/preferences/browser/media/regex-dark.svg1
-rw-r--r--src/vs/workbench/parts/preferences/browser/media/regex.svg1
-rw-r--r--src/vs/workbench/parts/preferences/browser/preferencesEditor.ts132
-rw-r--r--src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts218
-rw-r--r--src/vs/workbench/parts/preferences/browser/preferencesSearch.ts326
-rw-r--r--src/vs/workbench/parts/preferences/browser/preferencesService.ts125
-rw-r--r--src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts84
-rw-r--r--src/vs/workbench/parts/preferences/common/preferences.ts31
-rw-r--r--src/vs/workbench/parts/preferences/common/preferencesContribution.ts10
-rw-r--r--src/vs/workbench/parts/preferences/common/preferencesModels.ts428
13 files changed, 1048 insertions, 363 deletions
diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts
index 10144ee2c71..86b39b2e234 100644
--- a/src/vs/base/browser/ui/checkbox/checkbox.ts
+++ b/src/vs/base/browser/ui/checkbox/checkbox.ts
@@ -73,6 +73,10 @@ export class Checkbox extends Widget {
});
}
+ public get enabled(): boolean {
+ return this.domNode.getAttribute('aria-disabled') !== 'true';
+ }
+
public focus(): void {
this.domNode.focus();
}
diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts
index 6387586a777..87de097e471 100644
--- a/src/vs/workbench/electron-browser/main.contribution.ts
+++ b/src/vs/workbench/electron-browser/main.contribution.ts
@@ -197,6 +197,16 @@ let workbenchProperties: { [path: string]: IJSONSchema; } = {
'description': nls.localize('openDefaultSettings', "Controls if opening settings also opens an editor showing all default settings."),
'default': true
},
+ 'workbench.settings.experimentalFuzzySearchEndpoint': {
+ 'type': 'string',
+ 'description': nls.localize('experimentalFuzzySearchEndpoint', "Indicates the endpoint to use for the experimental settings search."),
+ 'default': ''
+ },
+ 'workbench.settings.experimentalFuzzySearchKey': {
+ 'type': 'string',
+ 'description': nls.localize('experimentalFuzzySearchKey', "Indicates the key to use for the experimental settings search."),
+ 'default': ''
+ },
'workbench.sideBar.location': {
'type': 'string',
'enum': ['left', 'right'],
diff --git a/src/vs/workbench/parts/preferences/browser/media/preferences.css b/src/vs/workbench/parts/preferences/browser/media/preferences.css
index 22b490dcd6b..4084078ea86 100644
--- a/src/vs/workbench/parts/preferences/browser/media/preferences.css
+++ b/src/vs/workbench/parts/preferences/browser/media/preferences.css
@@ -73,18 +73,38 @@
padding-right: 32px;
}
-.settings-header-widget > .settings-count-widget {
+.settings-header-widget > .settings-search-controls > .settings-count-widget {
margin: 6px 0px;
padding: 0px 8px;
+ border-radius: 2px;
+ float: left;
+}
+
+.settings-header-widget > .settings-search-controls {
position: absolute;
right: 10px;
- border-radius: 2px;
}
-.settings-header-widget > .settings-count-widget.hide {
+.settings-header-widget > .settings-search-controls > .settings-count-widget.hide {
+ display: none;
+}
+
+.settings-header-widget > .settings-search-controls > .prefs-fuzzy-search-toggle {
+ margin: 5px 3px 5px 0px;
+}
+
+.settings-header-widget > .settings-search-controls > .prefs-fuzzy-search-toggle.hidden {
display: none;
}
+.vs .settings-header-widget > .settings-search-controls > .prefs-fuzzy-search-toggle {
+ background: url('regex.svg') center center no-repeat;
+}
+
+.vs-dark .settings-header-widget > .settings-search-controls > .prefs-fuzzy-search-toggle {
+ background: url('regex-dark.svg') center center no-repeat;
+}
+
.settings-header-widget > .settings-search-container {
flex: 1;
}
@@ -106,8 +126,13 @@
padding-left:10px;
}
+.monaco-editor .view-zones > .settings-header-widget {
+ z-index: 1;
+}
+
.monaco-editor .settings-header-widget .title-container {
display: flex;
+ user-select: none;
}
.vs .monaco-editor .settings-header-widget .title-container {
@@ -130,6 +155,16 @@
white-space: nowrap;
}
+.monaco-editor .settings-header-widget .title-container .settings-header-fuzzy-link {
+ margin-left: 4px;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.monaco-editor .settings-header-widget .title-container .settings-header-fuzzy-link.hidden {
+ display: none;
+}
+
.monaco-editor .settings-group-title-widget {
z-index: 1;
}
diff --git a/src/vs/workbench/parts/preferences/browser/media/regex-dark.svg b/src/vs/workbench/parts/preferences/browser/media/regex-dark.svg
new file mode 100644
index 00000000000..c303032e6a9
--- /dev/null
+++ b/src/vs/workbench/parts/preferences/browser/media/regex-dark.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#2d2d30" points="13.64,7.396 12.169,2.898 10.706,3.761 11.087,2 6.557,2 6.936,3.762 5.473,2.898 4,7.396 5.682,7.554 4.513,8.561 5.013,9 2,9 2,14 7,14 7,10.747 7.978,11.606 8.82,9.725 9.661,11.602 13.144,8.562 11.968,7.554"/><g fill="#C5C5C5"><path d="M12.301 6.518l-2.772.262 2.086 1.788-1.594 1.392-1.201-2.682-1.201 2.682-1.583-1.392 2.075-1.788-2.771-.262.696-2.126 2.358 1.392-.599-2.784h2.053l-.602 2.783 2.359-1.392.696 2.127z"/><rect x="3" y="10" width="3" height="3"/></g></svg> \ No newline at end of file
diff --git a/src/vs/workbench/parts/preferences/browser/media/regex.svg b/src/vs/workbench/parts/preferences/browser/media/regex.svg
new file mode 100644
index 00000000000..c677843beef
--- /dev/null
+++ b/src/vs/workbench/parts/preferences/browser/media/regex.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#F6F6F6" points="13.64,7.396 12.169,2.898 10.706,3.761 11.087,2 6.557,2 6.936,3.762 5.473,2.898 4,7.396 5.682,7.554 4.513,8.561 5.013,9 2,9 2,14 7,14 7,10.747 7.978,11.606 8.82,9.725 9.661,11.602 13.144,8.562 11.968,7.554"/><g fill="#424242"><path d="M12.301 6.518l-2.772.262 2.086 1.788-1.594 1.392-1.201-2.682-1.201 2.682-1.583-1.392 2.075-1.788-2.771-.262.696-2.126 2.358 1.392-.599-2.784h2.053l-.602 2.783 2.359-1.392.696 2.127z"/><rect x="3" y="10" width="3" height="3"/></g></svg> \ No newline at end of file
diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts
index a20434dd807..4ae357dac84 100644
--- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts
+++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts
@@ -6,8 +6,9 @@
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 * as DOM from 'vs/base/browser/dom';
-import { Delayer } from 'vs/base/common/async';
+import { Delayer, ThrottledDelayer } from 'vs/base/common/async';
import { Dimension, Builder } from 'vs/base/browser/builder';
import { ArrayNavigator, INavigator } from 'vs/base/common/iterator';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -29,6 +30,7 @@ import { SettingsEditorModel, DefaultSettingsEditorModel } from 'vs/workbench/pa
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
import { ICodeEditor, IEditorContributionCtor } from 'vs/editor/browser/editorBrowser';
import { SearchWidget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
+import { PreferencesSearchProvider, PreferencesSearchModel } from 'vs/workbench/parts/preferences/browser/preferencesSearch';
import { ContextKeyExpr, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Command } from 'vs/editor/common/editorCommonExtensions';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -85,10 +87,10 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput {
}
matches(other: any): boolean {
- if (!super.matches(other)) {
- return false;
+ if (other instanceof DefaultPreferencesEditorInput) {
+ return true;
}
- if (!(other instanceof DefaultPreferencesEditorInput)) {
+ if (!super.matches(other)) {
return false;
}
return true;
@@ -106,8 +108,10 @@ export class PreferencesEditor extends BaseEditor {
private settingsTargetsWidget: SettingsTargetsWidget;
private sideBySidePreferencesWidget: SideBySidePreferencesWidget;
private preferencesRenderers: PreferencesRenderers;
+ private searchProvider: PreferencesSearchProvider;
private delayedFilterLogging: Delayer<void>;
+ private filterThrottle: ThrottledDelayer<void>;
private latestEmptyFilters: string[] = [];
private lastFocusedWidget: SearchWidget | SideBySidePreferencesWidget = null;
@@ -126,6 +130,8 @@ 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.searchProvider = this.instantiationService.createInstance(PreferencesSearchProvider);
+ this.filterThrottle = new ThrottledDelayer(200);
}
public createEditor(parent: Builder): void {
@@ -139,7 +145,9 @@ export class PreferencesEditor extends BaseEditor {
placeholder: nls.localize('SearchSettingsWidget.Placeholder', "Search Settings"),
focusKey: this.focusSettingsContextKey
}));
- this._register(this.searchWidget.onDidChange(value => this.filterPreferences(value.trim())));
+ this.searchWidget.setFuzzyToggleVisible(this.searchProvider.remoteSearchEnabled);
+ this._register(this.searchProvider.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;
@@ -153,6 +161,11 @@ export class PreferencesEditor extends BaseEditor {
this.preferencesRenderers = this._register(new PreferencesRenderers());
this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(() => this.onWorkspaceFoldersChanged()));
this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this.onWorkbenchStateChanged()));
+
+ this._register(this.preferencesRenderers.onTriggeredFuzzy(() => {
+ this.searchWidget.fuzzyEnabled = true;
+ this.filterPreferences();
+ }));
}
public clearSearchResults(): void {
@@ -233,10 +246,22 @@ export class PreferencesEditor extends BaseEditor {
return this.sideBySidePreferencesWidget.setInput(<DefaultPreferencesEditorInput>newInput.details, <EditorInput>newInput.master, options).then(({ defaultPreferencesRenderer, editablePreferencesRenderer }) => {
this.preferencesRenderers.defaultPreferencesRenderer = defaultPreferencesRenderer;
this.preferencesRenderers.editablePreferencesRenderer = editablePreferencesRenderer;
- this.filterPreferences(this.searchWidget.getValue());
+ this.onInputChanged();
});
}
+ private onInputChanged(): void {
+ if (this.searchWidget.fuzzyEnabled) {
+ this.triggerThrottledFilter();
+ } else {
+ this.filterPreferences();
+ }
+ }
+
+ private triggerThrottledFilter(): void {
+ this.filterThrottle.trigger(() => this.filterPreferences());
+ }
+
private getSettingsConfigurationTarget(resource: URI): ConfigurationTarget {
if (this.preferencesService.userSettingsResource.toString() === resource.toString()) {
return ConfigurationTarget.USER;
@@ -300,14 +325,16 @@ export class PreferencesEditor extends BaseEditor {
promise.done(value => this.preferencesService.switchSettings(this.getSettingsConfigurationTarget(resource), resource));
}
- private filterPreferences(filter: string) {
- const count = this.preferencesRenderers.filterPreferences(filter);
- const message = filter ? this.showSearchResultsMessage(count) : nls.localize('totalSettingsMessage', "Total {0} Settings", count);
- this.searchWidget.showMessage(message, count);
- if (count === 0) {
- this.latestEmptyFilters.push(filter);
- }
- this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(filter));
+ private filterPreferences(): TPromise<void> {
+ const filter = this.searchWidget.getValue().trim();
+ return this.preferencesRenderers.filterPreferences(filter, this.searchProvider, this.searchWidget.fuzzyEnabled).then(count => {
+ const message = filter ? this.showSearchResultsMessage(count) : nls.localize('totalSettingsMessage', "Total {0} Settings", count);
+ this.searchWidget.showMessage(message, count);
+ if (count === 0) {
+ this.latestEmptyFilters.push(filter);
+ }
+ this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(filter));
+ }, onUnexpectedError);
}
private showSearchResultsMessage(count: number): string {
@@ -381,9 +408,13 @@ class PreferencesRenderers extends Disposable {
private _defaultPreferencesRenderer: IPreferencesRenderer<ISetting>;
private _editablePreferencesRenderer: IPreferencesRenderer<ISetting>;
private _settingsNavigator: SettingsNavigator;
+ private _filtersInProgress: TPromise<any>[];
private _disposables: IDisposable[] = [];
+ private _onTriggeredFuzzy: Emitter<void> = new Emitter<void>();
+ public onTriggeredFuzzy: Event<void> = this._onTriggeredFuzzy.event;
+
public get defaultPreferencesRenderer(): IPreferencesRenderer<ISetting> {
return this._defaultPreferencesRenderer;
}
@@ -398,6 +429,9 @@ class PreferencesRenderers extends Disposable {
this._defaultPreferencesRenderer.onUpdatePreference(({ key, value, source }) => this._updatePreference(key, value, source, this._editablePreferencesRenderer), this, this._disposables);
this._defaultPreferencesRenderer.onFocusPreference(preference => this._focusPreference(preference, this._editablePreferencesRenderer), this, this._disposables);
this._defaultPreferencesRenderer.onClearFocusPreference(preference => this._clearFocus(preference, this._editablePreferencesRenderer), this, this._disposables);
+ if (this._defaultPreferencesRenderer.onTriggeredFuzzy) {
+ this._register(this._defaultPreferencesRenderer.onTriggeredFuzzy(() => this._onTriggeredFuzzy.fire()));
+ }
}
}
}
@@ -406,19 +440,37 @@ class PreferencesRenderers extends Disposable {
this._editablePreferencesRenderer = editableSettingsRenderer;
}
- public filterPreferences(filter: string): number {
- const defaultPreferencesFilterResult = this._filterPreferences(filter, this._defaultPreferencesRenderer);
- const editablePreferencesFilterResult = this._filterPreferences(filter, this._editablePreferencesRenderer);
+ public filterPreferences(filter: string, searchProvider: PreferencesSearchProvider, fuzzy: boolean): TPromise<number> {
+ if (this._filtersInProgress) {
+ // Resolved/rejected promises have no .cancel()
+ this._filtersInProgress.forEach(p => p.cancel && p.cancel());
+ }
+
+ const searchModel = searchProvider.startSearch(filter, fuzzy);
+ this._filtersInProgress = [
+ this._filterPreferences(searchModel, searchProvider, this._defaultPreferencesRenderer),
+ this._filterPreferences(searchModel, searchProvider, this._editablePreferencesRenderer)];
+
+ return TPromise.join<IFilterResult>(this._filtersInProgress).then(filterResults => {
+ this._filtersInProgress = null;
+ const defaultPreferencesFilterResult = filterResults[0];
+ const editablePreferencesFilterResult = filterResults[1];
+
+ const defaultPreferencesFilteredGroups = defaultPreferencesFilterResult ? defaultPreferencesFilterResult.filteredGroups : this._getAllPreferences(this._defaultPreferencesRenderer);
+ const editablePreferencesFilteredGroups = editablePreferencesFilterResult ? editablePreferencesFilterResult.filteredGroups : this._getAllPreferences(this._editablePreferencesRenderer);
+ const consolidatedSettings = this._consolidateSettings(editablePreferencesFilteredGroups, defaultPreferencesFilteredGroups);
- const defaultPreferencesFilteredGroups = defaultPreferencesFilterResult ? defaultPreferencesFilterResult.filteredGroups : this._getAllPreferences(this._defaultPreferencesRenderer);
- const editablePreferencesFilteredGroups = editablePreferencesFilterResult ? editablePreferencesFilterResult.filteredGroups : this._getAllPreferences(this._editablePreferencesRenderer);
- const consolidatedSettings = this._consolidateSettings(editablePreferencesFilteredGroups, defaultPreferencesFilteredGroups);
- this._settingsNavigator = new SettingsNavigator(filter ? consolidatedSettings : []);
+ this._settingsNavigator = new SettingsNavigator(filter ? consolidatedSettings : []);
- return consolidatedSettings.length;
+ return consolidatedSettings.length;
+ });
}
public focusNextPreference(forward: boolean = true) {
+ if (!this._settingsNavigator) {
+ return;
+ }
+
const setting = forward ? this._settingsNavigator.next() : this._settingsNavigator.previous();
this._focusPreference(setting, this._defaultPreferencesRenderer);
this._focusPreference(setting, this._editablePreferencesRenderer);
@@ -428,13 +480,17 @@ class PreferencesRenderers extends Disposable {
return preferencesRenderer ? (<ISettingsEditorModel>preferencesRenderer.preferencesModel).settingsGroups : [];
}
- private _filterPreferences(filter: string, preferencesRenderer: IPreferencesRenderer<ISetting>): IFilterResult {
- let filterResult = null;
+ private _filterPreferences(searchModel: PreferencesSearchModel, searchProvider: PreferencesSearchProvider, preferencesRenderer: IPreferencesRenderer<ISetting>): TPromise<IFilterResult> {
if (preferencesRenderer) {
- filterResult = filter ? (<ISettingsEditorModel>preferencesRenderer.preferencesModel).filterSettings(filter) : null;
- preferencesRenderer.filterPreferences(filterResult);
+ const prefSearchP = searchModel.filterPreferences(<ISettingsEditorModel>preferencesRenderer.preferencesModel);
+
+ return prefSearchP.then(filterResult => {
+ preferencesRenderer.filterPreferences(filterResult, searchProvider.remoteSearchEnabled);
+ return filterResult;
+ });
}
- return filterResult;
+
+ return TPromise.wrap(null);
}
private _focusPreference(preference: ISetting, preferencesRenderer: IPreferencesRenderer<ISetting>): void {
@@ -550,6 +606,9 @@ class SideBySidePreferencesWidget extends Widget {
}
public clearInput(): void {
+ if (this.defaultPreferencesEditor) {
+ this.defaultPreferencesEditor.clearInput();
+ }
if (this.editablePreferencesEditor) {
this.editablePreferencesEditor.clearInput();
}
@@ -765,6 +824,14 @@ export class DefaultPreferencesEditor extends BaseTextEditor {
.then(editorModel => this.getControl().setModel((<ResourceEditorModel>editorModel).textEditorModel)));
}
+ public clearInput(): void {
+ // Clear Model
+ this.getControl().setModel(null);
+
+ // Pass to super
+ super.clearInput();
+ }
+
public layout(dimension: Dimension) {
this.getControl().layout(dimension);
}
@@ -854,6 +921,7 @@ abstract class AbstractSettingsEditorContribution extends Disposable {
if (preferencesRenderer.associatedPreferencesModel) {
preferencesRenderer.associatedPreferencesModel.dispose();
}
+ preferencesRenderer.preferencesModel.dispose();
preferencesRenderer.dispose();
}
});
@@ -910,16 +978,16 @@ class SettingsEditorContribution extends AbstractSettingsEditorContribution impl
protected _createPreferencesRenderer(): TPromise<IPreferencesRenderer<ISetting>> {
if (this.isSettingsModel()) {
- return TPromise.join<any>([this.preferencesService.createPreferencesEditorModel(this.preferencesService.defaultSettingsResource), this.preferencesService.createPreferencesEditorModel(this.editor.getModel().uri)])
- .then(([defaultSettingsModel, settingsModel]) => {
+ return this.preferencesService.createPreferencesEditorModel(this.editor.getModel().uri)
+ .then(settingsModel => {
if (settingsModel instanceof SettingsEditorModel && this.editor.getModel()) {
switch (settingsModel.configurationTarget) {
case ConfigurationTarget.USER:
- return this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel, defaultSettingsModel);
+ return this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel);
case ConfigurationTarget.WORKSPACE:
- return this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel, defaultSettingsModel);
+ return this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel);
case ConfigurationTarget.WORKSPACE_FOLDER:
- return this.instantiationService.createInstance(FolderSettingsRenderer, this.editor, settingsModel, defaultSettingsModel);
+ return this.instantiationService.createInstance(FolderSettingsRenderer, this.editor, settingsModel);
}
}
return null;
diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts
index 08edba32488..4795b7be614 100644
--- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts
+++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts
@@ -6,6 +6,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import { Delayer } from 'vs/base/common/async';
+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';
@@ -19,7 +20,7 @@ import { IPreferencesService, ISettingsGroup, ISetting, IPreferencesEditorModel,
import { SettingsEditorModel, DefaultSettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { IContextMenuService, ContextSubMenu } from 'vs/platform/contextview/browser/contextView';
-import { SettingsGroupTitleWidget, EditPreferenceWidget, SettingsHeaderWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
+import { SettingsGroupTitleWidget, EditPreferenceWidget, SettingsHeaderWidget, DefaultSettingsHeaderWidget, FloatingClickWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { RangeHighlightDecorations } from 'vs/workbench/common/editor/rangeDecorations';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@@ -40,21 +41,22 @@ export interface IPreferencesRenderer<T> extends IDisposable {
onFocusPreference: Event<T>;
onClearFocusPreference: Event<T>;
onUpdatePreference: Event<{ key: string, value: any, source: T }>;
+ onTriggeredFuzzy?: Event<void>;
render(): void;
updatePreference(key: string, value: any, source: T): void;
- filterPreferences(filterResult: IFilterResult): void;
+ filterPreferences(filterResult: IFilterResult, fuzzySearchAvailable: boolean): void;
focusPreference(setting: T): void;
clearFocus(setting: T): void;
}
-
export class UserSettingsRenderer extends Disposable implements IPreferencesRenderer<ISetting> {
private settingHighlighter: SettingHighlighter;
private editSettingActionRenderer: EditSettingRenderer;
private highlightMatchesRenderer: HighlightMatchesRenderer;
private modelChangeDelayer: Delayer<void> = new Delayer<void>(200);
+ private _associatedPreferencesModel: IPreferencesEditorModel<ISetting>;
private _onFocusPreference: Emitter<ISetting> = new Emitter<ISetting>();
public readonly onFocusPreference: Event<ISetting> = this._onFocusPreference.event;
@@ -67,7 +69,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend
private filterResult: IFilterResult;
- constructor(protected editor: ICodeEditor, public readonly preferencesModel: SettingsEditorModel, private _associatedPreferencesModel: IPreferencesEditorModel<ISetting>,
+ constructor(protected editor: ICodeEditor, public readonly preferencesModel: SettingsEditorModel,
@IPreferencesService protected preferencesService: IPreferencesService,
@ITelemetryService private telemetryService: ITelemetryService,
@ITextFileService private textFileService: ITextFileService,
@@ -76,7 +78,6 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend
@IInstantiationService protected instantiationService: IInstantiationService
) {
super();
- this._register(preferencesModel);
this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor, this._onFocusPreference, this._onClearFocusPreference));
this.highlightMatchesRenderer = this._register(instantiationService.createInstance(HighlightMatchesRenderer, editor));
this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.preferencesModel, this.settingHighlighter));
@@ -176,7 +177,7 @@ export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements I
private untrustedSettingRenderer: UnsupportedWorkspaceSettingsRenderer;
private workspaceConfigurationRenderer: WorkspaceConfigurationRenderer;
- constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel, associatedPreferencesModel: IPreferencesEditorModel<ISetting>,
+ constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel,
@IPreferencesService preferencesService: IPreferencesService,
@ITelemetryService telemetryService: ITelemetryService,
@ITextFileService textFileService: ITextFileService,
@@ -184,7 +185,7 @@ export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements I
@IMessageService messageService: IMessageService,
@IInstantiationService instantiationService: IInstantiationService
) {
- super(editor, preferencesModel, associatedPreferencesModel, preferencesService, telemetryService, textFileService, configurationService, messageService, instantiationService);
+ super(editor, preferencesModel, preferencesService, telemetryService, textFileService, configurationService, messageService, instantiationService);
this.untrustedSettingRenderer = this._register(instantiationService.createInstance(UnsupportedWorkspaceSettingsRenderer, editor, preferencesModel));
this.workspaceConfigurationRenderer = this._register(instantiationService.createInstance(WorkspaceConfigurationRenderer, editor, preferencesModel));
}
@@ -204,7 +205,7 @@ export class FolderSettingsRenderer extends UserSettingsRenderer implements IPre
private unsupportedWorkbenchSettingsRenderer: UnsupportedWorkbenchSettingsRenderer;
- constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel, associatedPreferencesModel: IPreferencesEditorModel<ISetting>,
+ constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel,
@IPreferencesService preferencesService: IPreferencesService,
@ITelemetryService telemetryService: ITelemetryService,
@ITextFileService textFileService: ITextFileService,
@@ -212,7 +213,7 @@ export class FolderSettingsRenderer extends UserSettingsRenderer implements IPre
@IMessageService messageService: IMessageService,
@IInstantiationService instantiationService: IInstantiationService
) {
- super(editor, preferencesModel, associatedPreferencesModel, preferencesService, telemetryService, textFileService, configurationService, messageService, instantiationService);
+ super(editor, preferencesModel, preferencesService, telemetryService, textFileService, configurationService, messageService, instantiationService);
this.unsupportedWorkbenchSettingsRenderer = this._register(instantiationService.createInstance(UnsupportedWorkbenchSettingsRenderer, editor, preferencesModel));
}
@@ -235,6 +236,7 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
private filteredMatchesRenderer: FilteredMatchesRenderer;
private hiddenAreasRenderer: HiddenAreasRenderer;
private editSettingActionRenderer: EditSettingRenderer;
+ private feedbackWidgetRenderer: FeedbackWidgetRenderer;
private _onUpdatePreference: Emitter<{ key: string, value: any, source: ISetting }> = new Emitter<{ key: string, value: any, source: ISetting }>();
public readonly onUpdatePreference: Event<{ key: string, value: any, source: ISetting }> = this._onUpdatePreference.event;
@@ -245,6 +247,8 @@ 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,
@@ -258,11 +262,17 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
this.settingsGroupTitleRenderer = this._register(instantiationService.createInstance(SettingsGroupTitleRenderer, editor));
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));
+
this._register(this.editSettingActionRenderer.onUpdateSetting(e => this._onUpdatePreference.fire(e)));
- const paranthesisHidingRenderer = this._register(instantiationService.createInstance(StaticContentHidingRenderer, editor, preferencesModel.settingsGroups));
- this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, paranthesisHidingRenderer]));
+ const parenthesisHidingRenderer = this._register(instantiationService.createInstance(StaticContentHidingRenderer, editor, preferencesModel.settingsGroups));
+
+ const hiddenAreasProviders = [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, parenthesisHidingRenderer];
+ this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, hiddenAreasProviders));
this._register(this.settingsGroupTitleRenderer.onHiddenAreasChanged(() => this.hiddenAreasRenderer.render()));
+
+ this.onTriggeredFuzzy = this.settingsHeaderRenderer.onClick;
}
public get associatedPreferencesModel(): IPreferencesEditorModel<ISetting> {
@@ -277,28 +287,32 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
public render() {
this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups);
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel);
+ this.feedbackWidgetRenderer.render(null);
this.hiddenAreasRenderer.render();
this.settingHighlighter.clear(true);
- this.settingsGroupTitleRenderer.showGroup(1);
+ this.settingsGroupTitleRenderer.showGroup(0);
this.hiddenAreasRenderer.render();
}
- public filterPreferences(filterResult: IFilterResult): void {
+ public filterPreferences(filterResult: IFilterResult, fuzzySearchAvailable: boolean): void {
this.filterResult = filterResult;
- if (!filterResult) {
- this.settingHighlighter.clear(true);
- this.filteredMatchesRenderer.render(null);
- this.settingsHeaderRenderer.render(this.preferencesModel.settingsGroups);
- this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups);
- this.settingsGroupTitleRenderer.showGroup(1);
- this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel);
- } else {
- this.filteredMatchesRenderer.render(filterResult);
- this.settingsHeaderRenderer.render(filterResult.filteredGroups);
+ if (filterResult) {
+ this.filteredMatchesRenderer.render(filterResult, this.preferencesModel.settingsGroups);
this.settingsGroupTitleRenderer.render(filterResult.filteredGroups);
+ this.feedbackWidgetRenderer.render(filterResult);
+ this.settingsHeaderRenderer.render(filterResult, fuzzySearchAvailable);
this.settingHighlighter.clear(true);
this.editSettingActionRenderer.render(filterResult.filteredGroups, this._associatedPreferencesModel);
+ } else {
+ this.settingHighlighter.clear(true);
+ this.filteredMatchesRenderer.render(null, this.preferencesModel.settingsGroups);
+ this.feedbackWidgetRenderer.render(null);
+ this.settingsHeaderRenderer.render(null);
+ this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups);
+ this.settingsGroupTitleRenderer.showGroup(0);
+ this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel);
}
+
this.hiddenAreasRenderer.render();
}
@@ -365,6 +379,9 @@ export class StaticContentHidingRenderer extends Disposable implements HiddenAre
get hiddenAreas(): IRange[] {
const model = this.editor.getModel();
+
+ // Hide extra chars for "search results" and "commonly used" groups
+ const lastGroup = tail(this.settingsGroups);
return [
{
startLineNumber: 1,
@@ -379,6 +396,12 @@ export class StaticContentHidingRenderer extends Disposable implements HiddenAre
endColumn: model.getLineMaxColumn(this.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(),
@@ -391,20 +414,20 @@ export class StaticContentHidingRenderer extends Disposable implements HiddenAre
class DefaultSettingsHeaderRenderer extends Disposable {
- private settingsHeaderWidget: SettingsHeaderWidget;
+ private settingsHeaderWidget: DefaultSettingsHeaderWidget;
+ public onClick: Event<void>;
constructor(private editor: ICodeEditor, scope: ConfigurationScope) {
super();
const title = scope === ConfigurationScope.RESOURCE ? nls.localize('defaultFolderSettingsTitle', "Default Folder Settings") : nls.localize('defaultSettingsTitle', "Default Settings");
- this.settingsHeaderWidget = this._register(new SettingsHeaderWidget(editor, title));
+ this.settingsHeaderWidget = this._register(new DefaultSettingsHeaderWidget(editor, title));
+ this.onClick = this.settingsHeaderWidget.onClick;
}
- public render(settingsGroups: ISettingsGroup[]) {
- if (settingsGroups.length) {
- this.settingsHeaderWidget.setMessage(nls.localize('defaultSettings', "Place your settings in the right hand side editor to override."));
- } else {
- this.settingsHeaderWidget.setMessage(nls.localize('noSettingsFound', "No Settings Found."));
- }
+ public render(filterResult: IFilterResult, fuzzySearchAvailable = false) {
+ const hasSettings = !filterResult || filterResult.filteredGroups.length > 0;
+ const promptFuzzy = fuzzySearchAvailable && filterResult && !filterResult.metadata;
+ this.settingsHeaderWidget.toggleMessage(hasSettings, promptFuzzy);
}
}
@@ -434,9 +457,17 @@ export class SettingsGroupTitleRenderer extends Disposable implements HiddenArea
public render(settingsGroups: ISettingsGroup[]) {
this.disposeWidgets();
+ if (!settingsGroups) {
+ return;
+ }
+
this.settingsGroups = settingsGroups.slice();
this.settingsGroupTitleWidgets = [];
for (const group of this.settingsGroups.slice().reverse()) {
+ if (group.sections.every(sect => sect.settings.length === 0)) {
+ continue;
+ }
+
const settingsGroupTitleWidget = this.instantiationService.createInstance(SettingsGroupTitleWidget, this.editor, group);
settingsGroupTitleWidget.render();
this.settingsGroupTitleWidgets.push(settingsGroupTitleWidget);
@@ -446,9 +477,11 @@ export class SettingsGroupTitleRenderer extends Disposable implements HiddenArea
this.settingsGroupTitleWidgets.reverse();
}
- public showGroup(group: number) {
- this.hiddenGroups = this.settingsGroups.filter((g, i) => i !== group - 1);
- for (const groupTitleWidget of this.settingsGroupTitleWidgets.filter((g, i) => i !== group - 1)) {
+ public showGroup(groupIdx: number) {
+ const shownGroup = this.settingsGroupTitleWidgets[groupIdx].settingsGroup;
+
+ this.hiddenGroups = this.settingsGroups.filter(g => g !== shownGroup);
+ for (const groupTitleWidget of this.settingsGroupTitleWidgets.filter(widget => widget.settingsGroup !== shownGroup)) {
groupTitleWidget.toggleCollapse(true);
}
this._onHiddenAreasChanged.fire();
@@ -519,6 +552,115 @@ export class HiddenAreasRenderer extends Disposable {
}
}
+export class FeedbackWidgetRenderer extends Disposable {
+ private static COMMENT_TEXT = 'Modify the below results to match your expectations. Assign scores to indicate their relevance. Replace this comment with any text feedback.';
+
+ private _feedbackWidget: FloatingClickWidget;
+ private _currentResult: IFilterResult;
+
+ constructor(private editor: ICodeEditor,
+ @IInstantiationService private instantiationService: IInstantiationService,
+ @IWorkbenchEditorService private editorService: IWorkbenchEditorService,
+ @ITelemetryService private telemetryService: ITelemetryService
+ ) {
+ super();
+ }
+
+ public render(result: IFilterResult): void {
+ this._currentResult = result;
+ if (result && result.metadata) {
+ this.showWidget();
+ } else if (this._feedbackWidget) {
+ this.disposeWidget();
+ }
+ }
+
+ private showWidget(): void {
+ if (!this._feedbackWidget) {
+ this._feedbackWidget = this._register(this.instantiationService.createInstance(FloatingClickWidget, this.editor, 'Provide feedback', null));
+ this._register(this._feedbackWidget.onClick(() => this.getFeedback()));
+ this._feedbackWidget.render();
+ }
+ }
+
+ private getFeedback(): void {
+ const result = this._currentResult;
+ const actualResults = result.filteredGroups[0] ? result.filteredGroups[0].sections[0].settings.map(setting => setting.key) : [];
+
+ const feedbackQuery = {};
+ feedbackQuery['_comment'] = FeedbackWidgetRenderer.COMMENT_TEXT;
+ feedbackQuery['queryString'] = result.query;
+ feedbackQuery['resultScores'] = {};
+ actualResults.forEach(settingKey => {
+ feedbackQuery['resultScores'][settingKey] = 10;
+ });
+
+ const contents = JSON.stringify(feedbackQuery, undefined, ' ');
+ this.editorService.openEditor({ contents }, /*sideBySide=*/true).then(feedbackEditor => {
+ const sendFeedbackWidget = this._register(this.instantiationService.createInstance(FloatingClickWidget, feedbackEditor.getControl(), 'Send feedback', null));
+ sendFeedbackWidget.render();
+
+ this._register(sendFeedbackWidget.onClick(() => {
+ if (this.sendFeedback(feedbackEditor.getControl() as ICodeEditor, result, actualResults)) {
+ sendFeedbackWidget.dispose();
+ }
+ }));
+ });
+ }
+
+ private sendFeedback(feedbackEditor: ICodeEditor, result: IFilterResult, actualResults: string[]): boolean {
+ const model = feedbackEditor.getModel();
+ const expectedQueryLines = model.getLinesContent();
+ let expectedQuery: string;
+ try {
+ expectedQuery = JSON.parse(expectedQueryLines.join('\n'));
+ if (expectedQuery['_comment'] === FeedbackWidgetRenderer.COMMENT_TEXT) {
+ delete expectedQuery['_comment'];
+ }
+ } catch (e) {
+ // invalid JSON
+ }
+
+ if (expectedQuery) {
+ /* __GDPR__
+ "settingsSearchResultFeedback" : {
+ "query" : { "classification": "CustomContent", "purpose": "FeatureInsight" },
+ "userComment" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
+ "actualResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+ "expectedResults" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+ "url" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
+ "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
+ "timestamp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
+ }
+ */
+ this.telemetryService.publicLog('settingsSearchResultFeedback', {
+ query: result.query,
+ actualResults,
+ expectedQuery,
+ url: result.metadata.remoteUrl,
+ duration: result.metadata.duration,
+ timestamp: result.metadata.timestamp
+ });
+ return true;
+ }
+
+ return false;
+ }
+
+ private disposeWidget(): void {
+ if (this._feedbackWidget) {
+ this._feedbackWidget.dispose();
+ this._feedbackWidget = null;
+ }
+ }
+
+ public dispose() {
+ this.disposeWidget();
+
+ super.dispose();
+ }
+}
+
export class FilteredMatchesRenderer extends Disposable implements HiddenAreasProvider {
private decorationIds: string[] = [];
@@ -530,7 +672,7 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr
super();
}
- public render(result: IFilterResult): void {
+ public render(result: IFilterResult, allSettingsGroups: ISettingsGroup[]): void {
const model = this.editor.getModel();
this.hiddenAreas = [];
this.editor.changeDecorations(changeAccessor => {
@@ -541,6 +683,8 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr
this.editor.changeDecorations(changeAccessor => {
this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, result.matches.map(match => this.createDecoration(match, model)));
});
+ } else {
+ this.hiddenAreas = this.computeHiddenRanges(allSettingsGroups, allSettingsGroups, model);
}
}
@@ -559,7 +703,7 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr
const notMatchesRanges: IRange[] = [];
for (const group of allSettingsGroups) {
const filteredGroup = filteredGroups.filter(g => g.title === group.title)[0];
- if (!filteredGroup) {
+ if (!filteredGroup || filteredGroup.sections.every(sect => sect.settings.length === 0)) {
notMatchesRanges.push({
startLineNumber: group.range.startLineNumber - 1,
startColumn: model.getLineMinColumn(group.range.startLineNumber - 1),
@@ -886,8 +1030,8 @@ class EditSettingRenderer extends Disposable {
}
private getDefaultActions(setting: ISetting): IAction[] {
- const settingInOtherModel = this.associatedPreferencesModel.getPreference(setting.key);
if (this.isDefaultSettings()) {
+ const settingInOtherModel = this.associatedPreferencesModel.getPreference(setting.key);
return [<IAction>{
id: 'setDefaultValue',
label: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"),
diff --git a/src/vs/workbench/parts/preferences/browser/preferencesSearch.ts b/src/vs/workbench/parts/preferences/browser/preferencesSearch.ts
new file mode 100644
index 00000000000..10be6e89bdd
--- /dev/null
+++ b/src/vs/workbench/parts/preferences/browser/preferencesSearch.ts
@@ -0,0 +1,326 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { TPromise } from 'vs/base/common/winjs.base';
+import Event, { Emitter } from 'vs/base/common/event';
+import { ISettingsEditorModel, IFilterResult, ISetting, ISettingsGroup, IWorkbenchSettingsConfiguration, IFilterMetadata } from 'vs/workbench/parts/preferences/common/preferences';
+import { IRange, Range } from 'vs/editor/common/core/range';
+import { distinct } 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';
+import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
+import { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters';
+import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
+
+export interface IEndpointDetails {
+ urlBase: string;
+ key: string;
+}
+
+export class PreferencesSearchProvider {
+ private _onRemoteSearchEnablementChanged = new Emitter<boolean>();
+ public onRemoteSearchEnablementChanged: Event<boolean> = this._onRemoteSearchEnablementChanged.event;
+
+ constructor( @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService) {
+ configurationService.onDidChangeConfiguration(() => this._onRemoteSearchEnablementChanged.fire(this.remoteSearchEnabled));
+ }
+
+ get remoteSearchEnabled(): boolean {
+ const endpoint = this.endpoint;
+ return !!endpoint.urlBase && !!endpoint.key;
+ }
+
+ get endpoint(): IEndpointDetails {
+ const workbenchSettings = this.configurationService.getConfiguration<IWorkbenchSettingsConfiguration>().workbench.settings;
+ return {
+ urlBase: workbenchSettings.experimentalFuzzySearchEndpoint,
+ key: workbenchSettings.experimentalFuzzySearchKey
+ };
+ }
+
+ startSearch(filter: string, remote: boolean): PreferencesSearchModel {
+ return new PreferencesSearchModel(this, filter, remote);
+ }
+}
+
+export class PreferencesSearchModel {
+ private _localProvider: LocalSearchProvider;
+ private _remoteProvider: RemoteSearchProvider;
+
+ constructor(private provider: PreferencesSearchProvider, private filter: string, remote: boolean) {
+ this._localProvider = new LocalSearchProvider(filter);
+
+ if (remote && filter) {
+ this._remoteProvider = new 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 => {
+ return this._localProvider.filterPreferences(preferencesModel);
+ });
+ } else {
+ return this._localProvider.filterPreferences(preferencesModel);
+ }
+ }
+}
+
+class LocalSearchProvider {
+ 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);
+ };
+
+ const settingFilter = (setting: ISetting) => {
+ return new SettingMatches(this._filter, setting, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches;
+ };
+
+ return TPromise.wrap(preferencesModel.filterSettings(this._filter, groupFilter, settingFilter));
+ }
+}
+
+export interface IRemoteScores {
+ [key: string]: number;
+}
+
+interface IRemoteResult {
+ metadata: IFilterMetadata;
+ scores: IRemoteScores;
+}
+
+class RemoteSearchProvider {
+ private _filter: string;
+ private _remoteSearchP: TPromise<IRemoteResult>;
+
+ constructor(filter: string, endpoint: IEndpointDetails) {
+ this._filter = filter;
+ this._remoteSearchP = filter ? getSettingsFromBing(filter, endpoint) : TPromise.wrap(null);
+ }
+
+ filterPreferences(preferencesModel: ISettingsEditorModel): TPromise<IFilterResult> {
+ return this._remoteSearchP.then(remoteResult => {
+ const settingFilter = (setting: ISetting) => {
+ if (!!remoteResult.scores[setting.key]) {
+ const settingMatches = new SettingMatches(this._filter, setting, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches;
+ if (settingMatches.length) {
+ return settingMatches;
+ } else {
+ return [new Range(setting.keyRange.startLineNumber, setting.keyRange.startColumn, setting.keyRange.endLineNumber, setting.keyRange.startColumn)];
+ }
+ } else {
+ return null;
+ }
+ };
+
+ if (remoteResult) {
+ const sortedNames = Object.keys(remoteResult.scores).sort((a, b) => remoteResult.scores[b] - remoteResult.scores[a]);
+ const result = preferencesModel.filterSettings(this._filter, group => null, settingFilter, sortedNames);
+ result.metadata = remoteResult.metadata;
+ return result;
+ } else {
+ return null;
+ }
+ });
+ }
+}
+
+function getSettingsFromBing(filter: string, endpoint: IEndpointDetails): TPromise<IRemoteResult> {
+ const url = prepareUrl(filter, endpoint);
+ console.log('fetching: ' + url);
+ const start = Date.now();
+ const p = fetch(url, {
+ headers: new Headers({
+ 'User-Agent': 'request',
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'api-key': endpoint.key
+ })
+ })
+ .then(r => r.json())
+ .then(result => {
+ const timestamp = Date.now();
+ const duration = timestamp - start;
+ console.log('time: ' + duration / 1000);
+ const suggestions = (result.value || [])
+ .map(r => ({
+ name: r.setting || r.Setting,
+ score: r['@search.score']
+ }));
+
+ const scores = Object.create(null);
+ suggestions.forEach(s => {
+ const name = s.name
+ .replace(/^"/, '')
+ .replace(/"$/, '');
+ scores[name] = s.score;
+ });
+
+ return <IRemoteResult>{
+ metadata: {
+ remoteUrl: url,
+ duration,
+ timestamp
+ },
+ scores
+ };
+ });
+
+ return TPromise.as(p as any);
+}
+
+const API_VERSION = 'api-version=2015-02-28-Preview';
+const QUERY_TYPE = 'querytype=full';
+const SCORING_PROFILE = 'scoringProfile=ranking1';
+
+function escapeSpecialChars(query: string): string {
+ return query.replace(/\./g, ' ')
+ .replace(/[\\/+\-&|!"~*?:(){}\[\]\^]/g, '\\$&')
+ .replace(/ /g, ' ') // collapse spaces
+ .trim();
+}
+
+function prepareUrl(query: string, endpoint: IEndpointDetails): string {
+ query = escapeSpecialChars(query);
+ const userQuery = query;
+
+ // Appending Fuzzy after each word.
+ query = query.replace(/\ +/g, '~ ') + '~';
+
+ return `${endpoint.urlBase}?${API_VERSION}&search=${encodeURIComponent(userQuery + ' || ' + query)}&${QUERY_TYPE}&${SCORING_PROFILE}`;
+}
+
+class SettingMatches {
+
+ private readonly descriptionMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
+ private readonly keyMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
+ private readonly valueMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
+
+ public readonly matches: IRange[];
+
+ constructor(searchString: string, setting: ISetting, private valuesMatcher: (filter: string, setting: ISetting) => IRange[]) {
+ this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`);
+ }
+
+ private _findMatchesInSetting(searchString: string, setting: ISetting): IRange[] {
+ const result = this._doFindMatchesInSetting(searchString, setting);
+ if (setting.overrides && setting.overrides.length) {
+ for (const subSetting of setting.overrides) {
+ const subSettingMatches = new SettingMatches(searchString, subSetting, this.valuesMatcher);
+ let words = searchString.split(' ');
+ const descriptionRanges: IRange[] = this.getRangesForWords(words, this.descriptionMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]);
+ const keyRanges: IRange[] = this.getRangesForWords(words, this.keyMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]);
+ const subSettingKeyRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.keyMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.valueMatchingWords]);
+ const subSettinValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]);
+ result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettinValueRanges);
+ result.push(...subSettingMatches.matches);
+ }
+ }
+ return result;
+ }
+
+ private _doFindMatchesInSetting(searchString: string, setting: ISetting): IRange[] {
+ const registry: { [qualifiedKey: string]: IJSONSchema } = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
+ const schema: IJSONSchema = registry[setting.key];
+
+ let words = searchString.split(' ');
+ const settingKeyAsWords: string = setting.key.split('.').join(' ');
+
+ for (const word of words) {
+ for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) {
+ const descriptionMatches = matchesWords(word, setting.description[lineIndex], true);
+ if (descriptionMatches) {
+ this.descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex)));
+ }
+ }
+
+ const keyMatches = or(matchesWords, matchesCamelCase)(word, settingKeyAsWords);
+ if (keyMatches) {
+ this.keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match)));
+ }
+
+ const valueMatches = typeof setting.value === 'string' ? matchesContiguousSubString(word, setting.value) : null;
+ if (valueMatches) {
+ this.valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match)));
+ } else if (schema && schema.enum && schema.enum.some(enumValue => typeof enumValue === 'string' && !!matchesContiguousSubString(word, enumValue))) {
+ this.valueMatchingWords.set(word, []);
+ }
+ }
+
+ const descriptionRanges: IRange[] = [];
+ for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) {
+ const matches = or(matchesContiguousSubString)(searchString, setting.description[lineIndex] || '') || [];
+ descriptionRanges.push(...matches.map(match => this.toDescriptionRange(setting, match, lineIndex)));
+ }
+ if (descriptionRanges.length === 0) {
+ descriptionRanges.push(...this.getRangesForWords(words, this.descriptionMatchingWords, [this.keyMatchingWords, this.valueMatchingWords]));
+ }
+
+ const keyMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.key);
+ const keyRanges: IRange[] = keyMatches ? keyMatches.map(match => this.toKeyRange(setting, match)) : this.getRangesForWords(words, this.keyMatchingWords, [this.descriptionMatchingWords, this.valueMatchingWords]);
+
+ let valueRanges: IRange[] = [];
+ if (setting.value && typeof setting.value === 'string') {
+ const valueMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.value);
+ valueRanges = valueMatches ? valueMatches.map(match => this.toValueRange(setting, match)) : this.getRangesForWords(words, this.valueMatchingWords, [this.keyMatchingWords, this.descriptionMatchingWords]);
+ } else {
+ valueRanges = this.valuesMatcher(searchString, setting);
+ }
+
+ return [...descriptionRanges, ...keyRanges, ...valueRanges];
+ }
+
+ private getRangesForWords(words: string[], from: Map<string, IRange[]>, others: Map<string, IRange[]>[]): IRange[] {
+ const result: IRange[] = [];
+ for (const word of words) {
+ const ranges = from.get(word);
+ if (ranges) {
+ result.push(...ranges);
+ } else if (others.every(o => !o.has(word))) {
+ return [];
+ }
+ }
+ return result;
+ }
+
+ private toKeyRange(setting: ISetting, match: IMatch): IRange {
+ return {
+ startLineNumber: setting.keyRange.startLineNumber,
+ startColumn: setting.keyRange.startColumn + match.start,
+ endLineNumber: setting.keyRange.startLineNumber,
+ endColumn: setting.keyRange.startColumn + match.end
+ };
+ }
+
+ private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange {
+ return {
+ startLineNumber: setting.descriptionRanges[lineIndex].startLineNumber,
+ startColumn: setting.descriptionRanges[lineIndex].startColumn + match.start,
+ endLineNumber: setting.descriptionRanges[lineIndex].endLineNumber,
+ endColumn: setting.descriptionRanges[lineIndex].startColumn + match.end
+ };
+ }
+
+ private toValueRange(setting: ISetting, match: IMatch): IRange {
+ return {
+ startLineNumber: setting.valueRange.startLineNumber,
+ startColumn: setting.valueRange.startColumn + match.start + 1,
+ endLineNumber: setting.valueRange.startLineNumber,
+ endColumn: setting.valueRange.startColumn + match.end + 1
+ };
+ }
+} \ No newline at end of file
diff --git a/src/vs/workbench/parts/preferences/browser/preferencesService.ts b/src/vs/workbench/parts/preferences/browser/preferencesService.ts
index fdf08f805c2..5d866bac7ae 100644
--- a/src/vs/workbench/parts/preferences/browser/preferencesService.ts
+++ b/src/vs/workbench/parts/preferences/browser/preferencesService.ts
@@ -8,7 +8,6 @@ import * as network from 'vs/base/common/network';
import { TPromise } from 'vs/base/common/winjs.base';
import * as nls from 'vs/nls';
import URI from 'vs/base/common/uri';
-import { ResourceMap } from 'vs/base/common/map';
import * as labels from 'vs/base/common/labels';
import * as strings from 'vs/base/common/strings';
import { Disposable } from 'vs/base/common/lifecycle';
@@ -18,7 +17,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { Position as EditorPosition, IEditor, IEditorOptions } from 'vs/platform/editor/common/editor';
-import { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
+import { ICommonCodeEditor, IModel } from 'vs/editor/common/editorCommon';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
@@ -27,7 +26,7 @@ import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IPreferencesService, IPreferencesEditorModel, ISetting, getSettingsTargetName, FOLDER_SETTINGS_PATH, DEFAULT_SETTINGS_EDITOR_SETTING } from 'vs/workbench/parts/preferences/common/preferences';
-import { SettingsEditorModel, DefaultSettingsEditorModel, DefaultKeybindingsEditorModel, defaultKeybindingsContents, WorkspaceConfigModel } from 'vs/workbench/parts/preferences/common/preferencesModels';
+import { SettingsEditorModel, DefaultSettingsEditorModel, DefaultKeybindingsEditorModel, defaultKeybindingsContents, WorkspaceConfigModel, DefaultSettingsModel } from 'vs/workbench/parts/preferences/common/preferencesModels';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DefaultPreferencesEditorInput, PreferencesEditorInput } from 'vs/workbench/parts/preferences/browser/preferencesEditor';
import { KeybindingsEditorInput } from 'vs/workbench/parts/preferences/browser/keybindingsEditor';
@@ -40,6 +39,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
+import { IModeService } from 'vs/editor/common/services/modeService';
const emptyEditableSettingsContent = '{\n}';
@@ -47,12 +47,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic
_serviceBrand: any;
- // TODO:@sandy merge these models into editor inputs by extending resource editor model
- private defaultPreferencesEditorModels: ResourceMap<TPromise<IPreferencesEditorModel<any>>>;
private lastOpenedSettingsInput: PreferencesEditorInput = null;
private _onDispose: Emitter<void> = new Emitter<void>();
+ private _defaultSettingsUriCounter = 0;
+ private _defaultSettingsContentModel: DefaultSettingsModel;
+ private _defaultResourceSettingsUriCounter = 0;
+ private _defaultResourceSettingsContentModel: DefaultSettingsModel;
+
constructor(
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@@ -69,10 +72,10 @@ export class PreferencesService extends Disposable implements IPreferencesServic
@IExtensionService private extensionService: IExtensionService,
@IKeybindingService keybindingService: IKeybindingService,
@IModelService private modelService: IModelService,
- @IJSONEditingService private jsonEditingService: IJSONEditingService
+ @IJSONEditingService private jsonEditingService: IJSONEditingService,
+ @IModeService private modeService: IModeService
) {
super();
- this.defaultPreferencesEditorModels = new ResourceMap<TPromise<IPreferencesEditorModel<any>>>();
this.editorGroupService.onEditorsChanged(() => {
const activeEditorInput = this.editorService.getActiveEditorInput();
if (activeEditorInput instanceof PreferencesEditorInput) {
@@ -92,8 +95,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic
});
}
- readonly defaultSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/settings.json' });
- readonly defaultResourceSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/resourceSettings.json' });
readonly defaultKeybindingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' });
private readonly workspaceConfigSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'settings', path: '/workspaceSettings.json' });
@@ -109,54 +110,38 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, resource);
}
- resolveContent(uri: URI): TPromise<string> {
- const workspaceSettingsUri = this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE);
- if (workspaceSettingsUri && workspaceSettingsUri.toString() === uri.toString()) {
- return this.resolveSettingsContentFromWorkspaceConfiguration();
+ resolveModel(uri: URI): TPromise<IModel> {
+ if (this.isDefaultSettingsResource(uri) || this.isDefaultResourceSettingsResource(uri)) {
+ return this.extensionService.onReady()
+ .then(() => {
+ const scope = this.isDefaultSettingsResource(uri) ? ConfigurationScope.WINDOW : ConfigurationScope.RESOURCE;
+ const settingsModel = this.getDefaultSettingsModel(scope);
+ const mode = this.modeService.getOrCreateMode('json');
+ const model = this._register(this.modelService.createModel(settingsModel.content, mode, uri));
+ return model;
+ });
}
- return this.createPreferencesEditorModel(uri)
- .then(preferencesEditorModel => preferencesEditorModel ? preferencesEditorModel.content : null);
- }
- createPreferencesEditorModel(uri: URI): TPromise<IPreferencesEditorModel<any>> {
- let promise = this.defaultPreferencesEditorModels.get(uri);
- if (promise) {
- return promise;
+ if (this.defaultKeybindingsResource.toString() === uri.toString()) {
+ return this.extensionService.onReady()
+ .then(() => this.instantiationService.createInstance(DefaultKeybindingsEditorModel, uri).model);
}
- if (this.defaultSettingsResource.toString() === uri.toString()) {
- promise = TPromise.join<any>([this.extensionService.onReady(), this.fetchMostCommonlyUsedSettings()])
- .then(result => {
- const mostCommonSettings = result[1];
- const model = this.instantiationService.createInstance(DefaultSettingsEditorModel, uri, mostCommonSettings, ConfigurationScope.WINDOW);
- return model;
- });
- this.defaultPreferencesEditorModels.set(uri, promise);
- return promise;
- }
+ return TPromise.as(null);
+ }
- if (this.defaultResourceSettingsResource.toString() === uri.toString()) {
- promise = TPromise.join<any>([this.extensionService.onReady(), this.fetchMostCommonlyUsedSettings()])
- .then(result => {
- const mostCommonSettings = result[1];
- const model = this.instantiationService.createInstance(DefaultSettingsEditorModel, uri, mostCommonSettings, ConfigurationScope.RESOURCE);
- return model;
- });
- this.defaultPreferencesEditorModels.set(uri, promise);
- return promise;
+ createPreferencesEditorModel(uri: URI): TPromise<IPreferencesEditorModel<any>> {
+ if (this.isDefaultSettingsResource(uri) || this.isDefaultResourceSettingsResource(uri)) {
+ return this.createDefaultSettingsEditorModel(uri);
}
if (this.defaultKeybindingsResource.toString() === uri.toString()) {
const model = this.instantiationService.createInstance(DefaultKeybindingsEditorModel, uri);
- promise = TPromise.wrap(model);
- this.defaultPreferencesEditorModels.set(uri, promise);
- return promise;
+ return TPromise.wrap(model);
}
if (this.workspaceConfigSettingsResource.toString() === uri.toString()) {
- promise = this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE, uri);
- this.defaultPreferencesEditorModels.set(uri, promise);
- return promise;
+ return this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE, uri);
}
if (this.getEditableSettingsURI(ConfigurationTarget.USER).toString() === uri.toString()) {
@@ -268,11 +253,20 @@ export class PreferencesService extends Disposable implements IPreferencesServic
});
}
+ private isDefaultSettingsResource(uri: URI): boolean {
+ return uri.authority === 'defaultsettings' && uri.scheme === network.Schemas.vscode && !!uri.path.match(/\/(\d+\/)?settings\.json$/);
+ }
+
+ private isDefaultResourceSettingsResource(uri: URI): boolean {
+ return uri.authority === 'defaultsettings' && uri.scheme === network.Schemas.vscode && !!uri.path.match(/\/(\d+\/)?resourceSettings\.json$/);
+ }
+
private getDefaultSettingsResource(configurationTarget: ConfigurationTarget): URI {
if (configurationTarget === ConfigurationTarget.WORKSPACE_FOLDER) {
- return this.defaultResourceSettingsResource;
+ return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultResourceSettingsUriCounter++}/resourceSettings.json` });
+ } else {
+ return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultSettingsUriCounter++}/settings.json` });
}
- return this.defaultSettingsResource;
}
private getPreferencesEditorInputName(target: ConfigurationTarget, resource: URI): string {
@@ -298,17 +292,27 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return TPromise.wrap<SettingsEditorModel>(null);
}
- private resolveSettingsContentFromWorkspaceConfiguration(): TPromise<string> {
- if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
- return this.textModelResolverService.createModelReference(this.contextService.getWorkspace().configuration)
- .then(reference => {
- const model = reference.object.textEditorModel;
- const settingsContent = WorkspaceConfigModel.getSettingsContentFromConfigContent(model.getValue());
- reference.dispose();
- return TPromise.as(settingsContent ? settingsContent : emptyEditableSettingsContent);
- });
+ private createDefaultSettingsEditorModel(defaultSettingsUri: URI): TPromise<DefaultSettingsEditorModel> {
+ return this.textModelResolverService.createModelReference(defaultSettingsUri)
+ .then(reference => {
+ const scope = this.isDefaultSettingsResource(defaultSettingsUri) ? ConfigurationScope.WINDOW : ConfigurationScope.RESOURCE;
+ return this.instantiationService.createInstance(DefaultSettingsEditorModel, defaultSettingsUri, reference, scope, this.getDefaultSettingsModel(scope).settingsGroups);
+ });
+ }
+
+ private getDefaultSettingsModel(scope: ConfigurationScope): DefaultSettingsModel {
+ switch (scope) {
+ case ConfigurationScope.WINDOW:
+ if (!this._defaultSettingsContentModel) {
+ this._defaultSettingsContentModel = new DefaultSettingsModel(this.getMostCommonlyUsedSettings(), scope);
+ }
+ return this._defaultSettingsContentModel;
+ case ConfigurationScope.RESOURCE:
+ if (!this._defaultResourceSettingsContentModel) {
+ this._defaultResourceSettingsContentModel = new DefaultSettingsModel(this.getMostCommonlyUsedSettings(), scope);
+ }
+ return this._defaultResourceSettingsContentModel;
}
- return TPromise.as(null);
}
private getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): URI {
@@ -349,8 +353,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic
});
}
- private fetchMostCommonlyUsedSettings(): TPromise<string[]> {
- return TPromise.wrap([
+ private getMostCommonlyUsedSettings(): string[] {
+ return [
'files.autoSave',
'editor.fontSize',
'editor.fontFamily',
@@ -362,7 +366,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
'editor.wordWrap',
'files.exclude',
'files.associations'
- ]);
+ ];
}
private getPosition(language: string, codeEditor: ICommonCodeEditor): TPromise<IPosition> {
@@ -407,7 +411,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic
public dispose(): void {
this._onDispose.fire();
- this.defaultPreferencesEditorModels.clear();
super.dispose();
}
}
diff --git a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts
index 0eeeadfeb47..cf03fd27d6f 100644
--- a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts
+++ b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts
@@ -10,6 +10,7 @@ import * as DOM from 'vs/base/browser/dom';
import { TPromise } from 'vs/base/common/winjs.base';
import { Disposable } 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 } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
@@ -22,7 +23,7 @@ import { ISettingsGroup, IPreferencesService, getSettingsTargetName } from 'vs/w
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IAction, IActionRunner } from 'vs/base/common/actions';
-import { attachInputBoxStyler, attachStylerCallback, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
+import { attachInputBoxStyler, attachStylerCallback, attachSelectBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Position } from 'vs/editor/common/core/position';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
@@ -42,10 +43,10 @@ export class SettingsHeaderWidget extends Widget implements IViewZone {
private id: number;
private _domNode: HTMLElement;
- private titleContainer: HTMLElement;
+ protected titleContainer: HTMLElement;
private messageElement: HTMLElement;
- constructor(private editor: ICodeEditor, private title: string) {
+ constructor(protected editor: ICodeEditor, private title: string) {
super();
this.create();
this._register(this.editor.onDidChangeConfiguration(() => this.layout()));
@@ -64,7 +65,7 @@ export class SettingsHeaderWidget extends Widget implements IViewZone {
return 0;
}
- private create() {
+ protected create() {
this._domNode = DOM.$('.settings-header-widget');
this.titleContainer = DOM.append(this._domNode, DOM.$('.title-container'));
@@ -102,6 +103,38 @@ 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-fuzzy-link'));
+ this.linkElement.textContent = localize('defaultSettingsFuzzyPrompt', "Try fuzzy search!");
+
+ this.onclick(this.linkElement, e => this._onClick.fire());
+ this.toggleMessage(true);
+ }
+
+ public toggleMessage(hasSettings: boolean, promptFuzzy = false): 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');
+ }
+ }
+ }
+}
+
export class SettingsGroupTitleWidget extends Widget implements IViewZone {
private id: number;
@@ -400,6 +433,8 @@ 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>());
public readonly onDidChange: Event<string> = this._onDidChange.event;
@@ -417,10 +452,31 @@ 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.countElement = DOM.append(this.domNode, DOM.$('.settings-count-widget'));
+ this.controlsDiv = DOM.append(this.domNode, DOM.$('div.settings-search-controls'));
+ this.fuzzyToggle = this._register(new Checkbox({
+ actionClassName: 'prefs-fuzzy-search-toggle',
+ isChecked: false,
+ onChange: () => {
+ this.inputBox.focus();
+ this._onDidChange.fire();
+ },
+ title: localize('enableFuzzySearch', 'Enable experimental fuzzy search')
+ }));
+ DOM.append(this.controlsDiv, this.fuzzyToggle.domNode);
+ this._register(attachCheckboxStyler(this.fuzzyToggle, this.themeService));
+
+ this.countElement = DOM.append(this.controlsDiv, DOM.$('.settings-count-widget'));
this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder }, colors => {
const background = colors.badgeBackground ? colors.badgeBackground.toString() : null;
const border = colors.contrastBorder ? colors.contrastBorder.toString() : null;
@@ -462,10 +518,20 @@ export class SearchWidget extends Widget {
this.countElement.textContent = message;
this.inputBox.inputElement.setAttribute('aria-label', message);
DOM.toggleClass(this.countElement, 'no-results', count === 0);
- this.inputBox.inputElement.style.paddingRight = DOM.getTotalWidth(this.countElement) + 20 + 'px';
+ this.inputBox.inputElement.style.paddingRight = this.getControlsWidth() + 'px';
this.styleCountElementForeground();
}
+ 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);
@@ -478,10 +544,14 @@ export class SearchWidget extends Widget {
this.inputBox.inputElement.style.paddingRight = '0px';
} else {
DOM.removeClass(this.countElement, 'hide');
- this.inputBox.inputElement.style.paddingRight = DOM.getTotalWidth(this.countElement) + 20 + 'px';
+ this.inputBox.inputElement.style.paddingRight = this.getControlsWidth() + 'px';
}
}
+ private getControlsWidth(): number {
+ return DOM.getTotalWidth(this.countElement) + DOM.getTotalWidth(this.fuzzyToggle.domNode) + 20;
+ }
+
public focus() {
this.inputBox.focus();
if (this.getValue()) {
diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts
index 4bd6f7acdef..05c4ff4ffe1 100644
--- a/src/vs/workbench/parts/preferences/common/preferences.ts
+++ b/src/vs/workbench/parts/preferences/common/preferences.ts
@@ -9,12 +9,23 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IEditor, Position, IEditorOptions } from 'vs/platform/editor/common/editor';
+import { IModel } from 'vs/editor/common/editorCommon';
import { IKeybindingItemEntry } from 'vs/workbench/parts/preferences/common/keybindingsEditorModel';
import { IRange } from 'vs/editor/common/core/range';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { join } from 'vs/base/common/paths';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
+export interface IWorkbenchSettingsConfiguration {
+ workbench: {
+ settings: {
+ openDefaultSettings: boolean;
+ experimentalFuzzySearchEndpoint: string;
+ experimentalFuzzySearchKey: string;
+ }
+ };
+}
+
export interface ISettingsGroup {
id: string;
range: IRange;
@@ -42,22 +53,34 @@ export interface ISetting {
}
export interface IFilterResult {
+ query: string;
filteredGroups: ISettingsGroup[];
allGroups: ISettingsGroup[];
matches: IRange[];
+ fuzzySearchAvailable?: boolean;
+ metadata?: IFilterMetadata;
+}
+
+export interface IFilterMetadata {
+ remoteUrl: string;
+ timestamp: number;
+ duration: number;
}
export interface IPreferencesEditorModel<T> {
uri: URI;
- content: string;
getPreference(key: string): T;
dispose(): void;
}
+export type IGroupFilter = (group: ISettingsGroup) => boolean;
+export type ISettingFilter = (setting: ISetting) => IRange[];
+
export interface ISettingsEditorModel extends IPreferencesEditorModel<ISetting> {
settingsGroups: ISettingsGroup[];
groupsTerms: string[];
- filterSettings(filter: string): IFilterResult;
+ filterSettings(filter: string, groupFilter: IGroupFilter, settingFilter: ISettingFilter, mostRelevantSettings?: string[]): IFilterResult;
+ findValueMatches(filter: string, setting: ISetting): IRange[];
}
export interface IKeybindingsEditorModel<T> extends IPreferencesEditorModel<T> {
@@ -68,13 +91,11 @@ export const IPreferencesService = createDecorator<IPreferencesService>('prefere
export interface IPreferencesService {
_serviceBrand: any;
- defaultSettingsResource: URI;
- defaultResourceSettingsResource: URI;
userSettingsResource: URI;
workspaceSettingsResource: URI;
getFolderSettingsResource(resource: URI): URI;
- resolveContent(uri: URI): TPromise<string>;
+ resolveModel(uri: URI): TPromise<IModel>;
createPreferencesEditorModel<T>(uri: URI): TPromise<IPreferencesEditorModel<T>>;
openGlobalSettings(options?: IEditorOptions, position?: Position): TPromise<IEditor>;
diff --git a/src/vs/workbench/parts/preferences/common/preferencesContribution.ts b/src/vs/workbench/parts/preferences/common/preferencesContribution.ts
index 665bb232104..b9021dc6437 100644
--- a/src/vs/workbench/parts/preferences/common/preferencesContribution.ts
+++ b/src/vs/workbench/parts/preferences/common/preferencesContribution.ts
@@ -120,15 +120,7 @@ export class PreferencesContribution implements IWorkbenchContribution {
return TPromise.as(schemaModel);
}
}
- return this.preferencesService.resolveContent(uri)
- .then(content => {
- if (content !== null && content !== void 0) {
- let mode = this.modeService.getOrCreateMode('json');
- const model = this.modelService.createModel(content, mode, uri);
- return TPromise.as(model);
- }
- return null;
- });
+ return this.preferencesService.resolveModel(uri);
}
});
}
diff --git a/src/vs/workbench/parts/preferences/common/preferencesModels.ts b/src/vs/workbench/parts/preferences/common/preferencesModels.ts
index 1233bd69199..90ddb2828d2 100644
--- a/src/vs/workbench/parts/preferences/common/preferencesModels.ts
+++ b/src/vs/workbench/parts/preferences/common/preferencesModels.ts
@@ -4,150 +4,27 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
-import * as strings from 'vs/base/common/strings';
import { assign } from 'vs/base/common/objects';
-import { distinct } from 'vs/base/common/arrays';
+import { tail } from 'vs/base/common/arrays';
import URI from 'vs/base/common/uri';
import { IReference } from 'vs/base/common/lifecycle';
import Event from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { visit, JSONVisitor } from 'vs/base/common/json';
import { IModel } from 'vs/editor/common/editorCommon';
-import { IJSONSchema } from 'vs/base/common/jsonSchema';
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 } from 'vs/workbench/parts/preferences/common/preferences';
+import { ISettingsEditorModel, IKeybindingsEditorModel, ISettingsGroup, ISetting, IFilterResult, ISettingsSection, IGroupFilter, ISettingFilter } from 'vs/workbench/parts/preferences/common/preferences';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
-import { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters';
import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
-import { IRange } from 'vs/editor/common/core/range';
+import { IRange, Range } from 'vs/editor/common/core/range';
import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
import { TPromise } from 'vs/base/common/winjs.base';
import { Queue } from 'vs/base/common/async';
import { IFileService } from 'vs/platform/files/common/files';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
-
-class SettingMatches {
-
- private readonly descriptionMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
- private readonly keyMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
- private readonly valueMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
-
- public readonly matches: IRange[];
-
- constructor(searchString: string, setting: ISetting, private valuesMatcher: (filter: string, setting: ISetting) => IRange[]) {
- this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`);
- }
-
- private _findMatchesInSetting(searchString: string, setting: ISetting): IRange[] {
- const result = this._doFindMatchesInSetting(searchString, setting);
- if (setting.overrides && setting.overrides.length) {
- for (const subSetting of setting.overrides) {
- const subSettingMatches = new SettingMatches(searchString, subSetting, this.valuesMatcher);
- let words = searchString.split(' ');
- const descriptionRanges: IRange[] = this.getRangesForWords(words, this.descriptionMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]);
- const keyRanges: IRange[] = this.getRangesForWords(words, this.keyMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]);
- const subSettingKeyRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.keyMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.valueMatchingWords]);
- const subSettinValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]);
- result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettinValueRanges);
- result.push(...subSettingMatches.matches);
- }
- }
- return result;
- }
-
- private _doFindMatchesInSetting(searchString: string, setting: ISetting): IRange[] {
- const registry: { [qualifiedKey: string]: IJSONSchema } = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
- const schema: IJSONSchema = registry[setting.key];
-
- let words = searchString.split(' ');
- const settingKeyAsWords: string = setting.key.split('.').join(' ');
-
- for (const word of words) {
- for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) {
- const descriptionMatches = matchesWords(word, setting.description[lineIndex], true);
- if (descriptionMatches) {
- this.descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex)));
- }
- }
-
- const keyMatches = or(matchesWords, matchesCamelCase)(word, settingKeyAsWords);
- if (keyMatches) {
- this.keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match)));
- }
-
- const valueMatches = typeof setting.value === 'string' ? matchesContiguousSubString(word, setting.value) : null;
- if (valueMatches) {
- this.valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match)));
- } else if (schema && schema.enum && schema.enum.some(enumValue => typeof enumValue === 'string' && !!matchesContiguousSubString(word, enumValue))) {
- this.valueMatchingWords.set(word, []);
- }
- }
-
- const descriptionRanges: IRange[] = [];
- for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) {
- const matches = or(matchesContiguousSubString)(searchString, setting.description[lineIndex] || '') || [];
- descriptionRanges.push(...matches.map(match => this.toDescriptionRange(setting, match, lineIndex)));
- }
- if (descriptionRanges.length === 0) {
- descriptionRanges.push(...this.getRangesForWords(words, this.descriptionMatchingWords, [this.keyMatchingWords, this.valueMatchingWords]));
- }
-
- const keyMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.key);
- const keyRanges: IRange[] = keyMatches ? keyMatches.map(match => this.toKeyRange(setting, match)) : this.getRangesForWords(words, this.keyMatchingWords, [this.descriptionMatchingWords, this.valueMatchingWords]);
-
- let valueRanges: IRange[] = [];
- if (setting.value && typeof setting.value === 'string') {
- const valueMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.value);
- valueRanges = valueMatches ? valueMatches.map(match => this.toValueRange(setting, match)) : this.getRangesForWords(words, this.valueMatchingWords, [this.keyMatchingWords, this.descriptionMatchingWords]);
- } else {
- valueRanges = this.valuesMatcher(searchString, setting);
- }
-
- return [...descriptionRanges, ...keyRanges, ...valueRanges];
- }
-
- private getRangesForWords(words: string[], from: Map<string, IRange[]>, others: Map<string, IRange[]>[]): IRange[] {
- const result: IRange[] = [];
- for (const word of words) {
- const ranges = from.get(word);
- if (ranges) {
- result.push(...ranges);
- } else if (others.every(o => !o.has(word))) {
- return [];
- }
- }
- return result;
- }
-
- private toKeyRange(setting: ISetting, match: IMatch): IRange {
- return {
- startLineNumber: setting.keyRange.startLineNumber,
- startColumn: setting.keyRange.startColumn + match.start,
- endLineNumber: setting.keyRange.startLineNumber,
- endColumn: setting.keyRange.startColumn + match.end
- };
- }
-
- private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange {
- return {
- startLineNumber: setting.descriptionRanges[lineIndex].startLineNumber,
- startColumn: setting.descriptionRanges[lineIndex].startColumn + match.start,
- endLineNumber: setting.descriptionRanges[lineIndex].endLineNumber,
- endColumn: setting.descriptionRanges[lineIndex].startColumn + match.end
- };
- }
-
- private toValueRange(setting: ISetting, match: IMatch): IRange {
- return {
- startLineNumber: setting.valueRange.startLineNumber,
- startColumn: setting.valueRange.startColumn + match.start + 1,
- endLineNumber: setting.valueRange.startLineNumber,
- endColumn: setting.valueRange.startColumn + match.end + 1
- };
- }
-}
-
+import { IModelService } from 'vs/editor/common/services/modelService';
+import { IModeService } from 'vs/editor/common/services/modeService';
export abstract class AbstractSettingsModel extends EditorModel {
@@ -155,12 +32,15 @@ export abstract class AbstractSettingsModel extends EditorModel {
return this.settingsGroups.map(group => '@' + group.id);
}
- protected doFilterSettings(filter: string, allGroups: ISettingsGroup[]): IFilterResult {
+ protected doFilterSettings(filter: string, groupFilter: IGroupFilter, settingFilter: ISettingFilter): IFilterResult {
+ const allGroups = this.settingsGroups;
+
if (!filter) {
return {
filteredGroups: allGroups,
allGroups,
- matches: []
+ matches: [],
+ query: filter
};
}
@@ -169,24 +49,27 @@ export abstract class AbstractSettingsModel extends EditorModel {
return {
filteredGroups: [group],
allGroups,
- matches: []
+ matches: [],
+ query: filter
};
}
const matches: IRange[] = [];
const filteredGroups: ISettingsGroup[] = [];
- const regex = strings.createRegExp(filter, false, { global: true });
for (const group of allGroups) {
- const groupMatched = regex.test(group.title);
+ const groupMatched = groupFilter(group);
const sections: ISettingsSection[] = [];
for (const section of group.sections) {
const settings: ISetting[] = [];
for (const setting of section.settings) {
- const settingMatches = new SettingMatches(filter, setting, (filter, setting) => this.findValueMatches(filter, setting)).matches;
- if (groupMatched || settingMatches.length > 0) {
+ const settingMatches = settingFilter(setting);
+ if (groupMatched || settingMatches && settingMatches.length) {
settings.push(setting);
}
- matches.push(...settingMatches);
+
+ if (settingMatches) {
+ matches.push(...settingMatches);
+ }
}
if (settings.length) {
sections.push({
@@ -206,7 +89,7 @@ export abstract class AbstractSettingsModel extends EditorModel {
});
}
}
- return { filteredGroups, matches, allGroups };
+ return { filteredGroups, matches, allGroups, query: filter };
}
private filterByGroupTerm(filter: string): ISettingsGroup {
@@ -232,7 +115,7 @@ export abstract class AbstractSettingsModel extends EditorModel {
public abstract settingsGroups: ISettingsGroup[];
- protected abstract findValueMatches(filter: string, setting: ISetting): IRange[];
+ public abstract findValueMatches(filter: string, setting: ISetting): IRange[];
}
export class SettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
@@ -270,8 +153,12 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti
return this.settingsModel.getValue();
}
- public filterSettings(filter: string): IFilterResult {
- return this.doFilterSettings(filter, this.settingsGroups);
+ public filterSettings(filter: string, groupFilter: IGroupFilter, settingFilter: ISettingFilter): IFilterResult {
+ return this.doFilterSettings(filter, groupFilter, settingFilter);
+ }
+
+ public findValueMatches(filter: string, setting: ISetting): IRange[] {
+ return this.settingsModel.findMatches(filter, setting.valueRange, false, false, null, false).map(match => match.range);
}
public save(): TPromise<any> {
@@ -282,10 +169,6 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti
return this.textFileService.save(this.uri);
}
- protected findValueMatches(filter: string, setting: ISetting): IRange[] {
- return this.settingsModel.findMatches(filter, setting.valueRange, false, false, null, false).map(match => match.range);
- }
-
private parse() {
const model = this.settingsModel;
const settings: ISetting[] = [];
@@ -592,18 +475,16 @@ export class WorkspaceConfigModel extends SettingsEditorModel implements ISettin
}
}
-export class DefaultSettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
+export class DefaultSettingsModel {
private _allSettingsGroups: ISettingsGroup[];
private _content: string;
- private _contentByLines: string[];
-
- constructor(private _uri: URI, private _mostCommonlyUsedSettingsKeys: string[], readonly configurationScope: ConfigurationScope) {
- super();
- }
+ private _settingsByName: Map<string, ISetting>;
- public get uri(): URI {
- return this._uri;
+ constructor(
+ private _mostCommonlyUsedSettingsKeys: string[],
+ readonly configurationScope: ConfigurationScope,
+ ) {
}
public get content(): string {
@@ -620,46 +501,29 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
return this._allSettingsGroups;
}
- public get mostCommonlyUsedSettings(): ISettingsGroup {
- return this.settingsGroups[0];
- }
-
- public filterSettings(filter: string): IFilterResult {
- return this.doFilterSettings(filter, this.settingsGroups);
- }
-
- public getPreference(key: string): ISetting {
- for (const group of this.settingsGroups) {
- for (const section of group.sections) {
- for (const setting of section.settings) {
- if (setting.key === key) {
- return setting;
- }
- }
- }
- }
- return null;
- }
-
private parse() {
const configurations = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurations().slice();
const settingsGroups = this.removeEmptySettingsGroups(configurations.sort(this.compareConfigurationNodes).reduce((result, config, index, array) => this.parseConfig(config, result, array), []));
+ this.initAllSettingsMap(settingsGroups);
const mostCommonlyUsed = this.getMostCommonlyUsedSettings(settingsGroups);
this._allSettingsGroups = [mostCommonlyUsed, ...settingsGroups];
this._content = this.toContent(mostCommonlyUsed, settingsGroups);
}
- private getMostCommonlyUsedSettings(allSettingsGroups: ISettingsGroup[]): ISettingsGroup {
- const map: Map<string, ISetting> = new Map<string, ISetting>();
+ private initAllSettingsMap(allSettingsGroups: ISettingsGroup[]): void {
+ this._settingsByName = new Map<string, ISetting>();
for (const group of allSettingsGroups) {
for (const section of group.sections) {
for (const setting of section.settings) {
- map.set(setting.key, setting);
+ this._settingsByName.set(setting.key, setting);
}
}
}
+ }
+
+ private getMostCommonlyUsedSettings(allSettingsGroups: ISettingsGroup[]): ISettingsGroup {
const settings = this._mostCommonlyUsedSettingsKeys.map(key => {
- const setting = map.get(key);
+ const setting = this._settingsByName.get(key);
if (setting) {
return <ISetting>{
description: setting.description,
@@ -773,25 +637,171 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
}
private toContent(mostCommonlyUsed: ISettingsGroup, settingsGroups: ISettingsGroup[]): string {
+ const builder = new SettingsContentBuilder();
+ builder.pushLine('[');
+ builder.pushGroups([mostCommonlyUsed]);
+ builder.pushLine(',');
+ builder.pushGroups(settingsGroups);
+ builder.pushLine(']');
+ return builder.getContent();
+ }
+
+}
+
+export class DefaultSettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
+
+ private _model: IModel;
+ private _settingsByName: Map<string, ISetting>;
+ private _mostRelevantLineOffset: number;
+
+ constructor(
+ private _uri: URI,
+ reference: IReference<ITextEditorModel>,
+ readonly configurationScope: ConfigurationScope,
+ readonly settingsGroups: ISettingsGroup[]
+ ) {
+ super();
+ this._model = reference.object.textEditorModel;
+ this._register(this.onDispose(() => reference.dispose()));
+
+ this.initAllSettingsMap();
+ this._mostRelevantLineOffset = tail(this.settingsGroups).range.endLineNumber + 2;
+ }
+
+ public get uri(): URI {
+ return this._uri;
+ }
+
+ public filterSettings(filter: string, groupFilter: IGroupFilter, settingFilter: ISettingFilter, mostRelevantSettings?: string[]): IFilterResult {
+ if (mostRelevantSettings) {
+ const builder = new SettingsContentBuilder(this._mostRelevantLineOffset - 1);
+ builder.pushLine(',');
+ const mostRelevantGroup = this.getMostRelevantSettings(mostRelevantSettings);
+ builder.pushGroups([mostRelevantGroup]);
+ builder.pushLine('');
+
+ // note: 1-indexed line numbers here
+ const mostRelevantContent = builder.getContent();
+ const mostRelevantEndLine = this._model.getLineCount();
+ this._model.applyEdits([
+ {
+ text: mostRelevantContent,
+ forceMoveMarkers: false,
+ range: new Range(this._mostRelevantLineOffset, 1, mostRelevantEndLine, 1),
+ identifier: { major: 1, minor: 0 }
+ }
+ ]);
+
+ return {
+ allGroups: [...this.settingsGroups, mostRelevantGroup],
+ filteredGroups: mostRelevantGroup.sections[0].settings.length ? [mostRelevantGroup] : [],
+ matches: [],
+ query: filter
+ };
+ } else {
+ // local
+ return this.doFilterSettings(filter, groupFilter, settingFilter);
+ }
+ }
+
+ public findValueMatches(filter: string, setting: ISetting): IRange[] {
+ return [];
+ }
+
+ public getPreference(key: string): ISetting {
+ for (const group of this.settingsGroups) {
+ for (const section of group.sections) {
+ for (const setting of section.settings) {
+ if (setting.key === key) {
+ return setting;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private initAllSettingsMap(): void {
+ this._settingsByName = new Map<string, ISetting>();
+ for (const group of this.settingsGroups) {
+ for (const section of group.sections) {
+ for (const setting of section.settings) {
+ this._settingsByName.set(setting.key, setting);
+ }
+ }
+ }
+ }
+
+ private getMostRelevantSettings(rankedSettingNames: string[]): ISettingsGroup {
+ const settings = rankedSettingNames.map(key => {
+ const setting = this._settingsByName.get(key);
+ if (setting) {
+ return <ISetting>{
+ description: setting.description,
+ key: setting.key,
+ value: setting.value,
+ range: null,
+ valueRange: null,
+ overrides: []
+ };
+ }
+ return null;
+ }).filter(setting => !!setting);
+
+ return <ISettingsGroup>{
+ id: 'mostRelevant',
+ range: null,
+ title: nls.localize('mostRelevant', "Most Relevant"),
+ titleRange: null,
+ sections: [
+ {
+ settings
+ }
+ ]
+ };
+ }
+}
+
+class SettingsContentBuilder {
+ private _contentByLines: string[];
+
+ get lines(): string[] {
+ return this._contentByLines;
+ }
+
+ private get lineCountWithOffset(): number {
+ return this._contentByLines.length + this._rangeOffset;
+ }
+
+ private get lastLine(): string {
+ return this._contentByLines[this._contentByLines.length - 1] || '';
+ }
+
+ constructor(private _rangeOffset = 0, private _maxLines = Infinity) {
this._contentByLines = [];
- this._contentByLines.push('[');
- this.pushGroups([mostCommonlyUsed]);
- this._contentByLines.push(',');
- this.pushGroups(settingsGroups);
- this._contentByLines.push(']');
- return this._contentByLines.join('\n');
}
- private pushGroups(settingsGroups: ISettingsGroup[]): void {
+ private offsetIndexToIndex(offsetIdx: number): number {
+ return offsetIdx - this._rangeOffset;
+ }
+
+ pushLine(...lineText: string[]): void {
+ this._contentByLines.push(...lineText);
+ }
+
+ pushGroups(settingsGroups: ISettingsGroup[]): void {
let lastSetting: ISetting = null;
this._contentByLines.push('{');
this._contentByLines.push('');
for (const group of settingsGroups) {
+ this._contentByLines.push('');
lastSetting = this.pushGroup(group);
}
if (lastSetting) {
- const content = this._contentByLines[lastSetting.range.endLineNumber - 2];
- this._contentByLines[lastSetting.range.endLineNumber - 2] = content.substring(0, content.length - 1);
+ // 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('}');
}
@@ -799,13 +809,12 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
private pushGroup(group: ISettingsGroup): ISetting {
const indent = ' ';
let lastSetting: ISetting = null;
- this._contentByLines.push('');
- let groupStart = this._contentByLines.length + 1;
+ let groupStart = this.lineCountWithOffset + 1;
for (const section of group.sections) {
if (section.title) {
- let sectionTitleStart = this._contentByLines.length + 1;
+ let sectionTitleStart = this.lineCountWithOffset + 1;
this.addDescription([section.title], indent, this._contentByLines);
- section.titleRange = { startLineNumber: sectionTitleStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length };
+ section.titleRange = { startLineNumber: sectionTitleStart, startColumn: 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length };
}
if (section.settings.length) {
@@ -813,38 +822,39 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
this.pushSetting(setting, indent);
lastSetting = setting;
}
- } else {
- this._contentByLines.push('// ' + nls.localize('noSettings', "No Settings"));
- this._contentByLines.push('');
}
}
- group.range = { startLineNumber: groupStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length };
+ group.range = { startLineNumber: groupStart, startColumn: 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length };
return lastSetting;
}
+ getContent(): string {
+ return this._contentByLines.join('\n');
+ }
+
private pushSetting(setting: ISetting, indent: string): void {
- const settingStart = this._contentByLines.length + 1;
+ const settingStart = this.lineCountWithOffset + 1;
setting.descriptionRanges = [];
const descriptionPreValue = indent + '// ';
for (const line of setting.description) {
this._contentByLines.push(descriptionPreValue + line);
- setting.descriptionRanges.push({ startLineNumber: this._contentByLines.length, startColumn: this._contentByLines[this._contentByLines.length - 1].indexOf(line) + 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length });
+ setting.descriptionRanges.push({ startLineNumber: this.lineCountWithOffset, startColumn: this.lastLine.indexOf(line) + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length });
}
let preValueConent = indent;
const keyString = JSON.stringify(setting.key);
preValueConent += keyString;
- setting.keyRange = { startLineNumber: this._contentByLines.length + 1, startColumn: preValueConent.indexOf(setting.key) + 1, endLineNumber: this._contentByLines.length + 1, endColumn: setting.key.length };
+ setting.keyRange = { startLineNumber: this.lineCountWithOffset + 1, startColumn: preValueConent.indexOf(setting.key) + 1, endLineNumber: this.lineCountWithOffset + 1, endColumn: setting.key.length };
preValueConent += ': ';
- const valueStart = this._contentByLines.length + 1;
+ const valueStart = this.lineCountWithOffset + 1;
this.pushValue(setting, preValueConent, indent);
- setting.valueRange = { startLineNumber: valueStart, startColumn: preValueConent.length + 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length + 1 };
+ setting.valueRange = { startLineNumber: valueStart, startColumn: preValueConent.length + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length + 1 };
this._contentByLines[this._contentByLines.length - 1] += ',';
this._contentByLines.push('');
- setting.range = { startLineNumber: settingStart, startColumn: 1, endLineNumber: this._contentByLines.length, endColumn: this._contentByLines[this._contentByLines.length - 1].length };
+ setting.range = { startLineNumber: settingStart, startColumn: 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length };
}
private pushValue(setting: ISetting, preValueConent: string, indent: string): void {
@@ -877,14 +887,6 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
result.push(indent + '// ' + line);
}
}
-
- protected findValueMatches(filter: string, setting: ISetting): IRange[] {
- return [];
- }
-
- public dispose(): void {
- // Not disposable
- }
}
export function defaultKeybindingsContents(keybindingService: IKeybindingService): string {
@@ -895,8 +897,16 @@ export function defaultKeybindingsContents(keybindingService: IKeybindingService
export class DefaultKeybindingsEditorModel implements IKeybindingsEditorModel<any> {
private _content: string;
+ private _model: IModel;
+
+ constructor(private _uri: URI,
+ @IKeybindingService private keybindingService: IKeybindingService,
+ @IModeService private modeService: IModeService,
+ @IModelService private modelService: IModelService) {
+ }
- constructor(private _uri: URI, @IKeybindingService private keybindingService: IKeybindingService) {
+ public get model(): IModel {
+ return this._model;
}
public get uri(): URI {