提交 ff89af04 编写于 作者: R Rob Lourens

Settings search from remote service

上级 af6ca44d
......@@ -29,6 +29,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 } 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';
......@@ -56,7 +57,7 @@ import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRe
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import Event, { Emitter } from 'vs/base/common/event';
import Event, { Emitter, debounceEvent } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
export class PreferencesEditorInput extends SideBySideEditorInput {
......@@ -107,6 +108,7 @@ export class PreferencesEditor extends BaseEditor {
private preferencesRenderers: PreferencesRenderers;
private delayedFilterLogging: Delayer<void>;
private onInput: Emitter<void>;
private latestEmptyFilters: string[] = [];
private lastFocusedWidget: SearchWidget | SideBySidePreferencesWidget = null;
......@@ -125,6 +127,11 @@ 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.onInput = new Emitter();
debounceEvent(this.onInput.event, (l, e) => e, 200, /*leading=*/true)(() => {
this.filterPreferences(this.searchWidget.getValue().trim());
});
}
public createEditor(parent: Builder): void {
......@@ -138,7 +145,7 @@ 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._register(this.searchWidget.onDidChange(value => this.onInput.fire()));
this._register(this.searchWidget.onFocus(() => this.lastFocusedWidget = this.searchWidget));
this.lastFocusedWidget = this.searchWidget;
......@@ -232,7 +239,7 @@ 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.onInput.fire();
});
}
......@@ -300,13 +307,14 @@ export class PreferencesEditor extends BaseEditor {
}
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));
this.preferencesRenderers.filterPreferences(filter).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));
});
}
private showSearchResultsMessage(count: number): string {
......@@ -380,6 +388,7 @@ class PreferencesRenderers extends Disposable {
private _defaultPreferencesRenderer: IPreferencesRenderer<ISetting>;
private _editablePreferencesRenderer: IPreferencesRenderer<ISetting>;
private _settingsNavigator: SettingsNavigator;
private _filtersInProgress: TPromise<any>[];
private _disposables: IDisposable[] = [];
......@@ -405,19 +414,41 @@ 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): TPromise<number> {
if (this._filtersInProgress) {
// Resolved/rejected promises have no .cancel()
this._filtersInProgress.forEach(p => p.cancel && p.cancel());
}
const searchProvider = new PreferencesSearchProvider(filter);
this._filtersInProgress = [
this._filterPreferences(filter, searchProvider, this._defaultPreferencesRenderer),
this._filterPreferences(filter, 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 : []);
if (defaultPreferencesFilterResult && defaultPreferencesFilterResult.scores) {
this._settingsNavigator = null;
} else {
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);
......@@ -427,13 +458,19 @@ class PreferencesRenderers extends Disposable {
return preferencesRenderer ? (<ISettingsEditorModel>preferencesRenderer.preferencesModel).settingsGroups : [];
}
private _filterPreferences(filter: string, preferencesRenderer: IPreferencesRenderer<ISetting>): IFilterResult {
let filterResult = null;
private _filterPreferences(filter: string, searchProvider: PreferencesSearchProvider, preferencesRenderer: IPreferencesRenderer<ISetting>): TPromise<IFilterResult> {
if (preferencesRenderer) {
filterResult = filter ? (<ISettingsEditorModel>preferencesRenderer.preferencesModel).filterSettings(filter) : null;
preferencesRenderer.filterPreferences(filterResult);
const prefSearchP = filter ?
searchProvider.filterPreferences(<ISettingsEditorModel>preferencesRenderer.preferencesModel) :
TPromise.wrap(null);
return prefSearchP.then(filterResult => {
preferencesRenderer.filterPreferences(filterResult);
return filterResult;
});
}
return filterResult;
return TPromise.wrap(null);
}
private _focusPreference(preference: ISetting, preferencesRenderer: IPreferencesRenderer<ISetting>): void {
......@@ -839,7 +876,7 @@ abstract class AbstractSettingsEditorContribution extends Disposable {
this.preferencesRendererCreationPromise.then(preferencesRenderer => {
if (preferencesRenderer) {
if (preferencesRenderer.associatedPreferencesModel) {
preferencesRenderer.associatedPreferencesModel.dispose();
this.preferencesService.disownPreferencesEditorModel(preferencesRenderer.associatedPreferencesModel);
}
preferencesRenderer.dispose();
}
......
......@@ -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 * as strings from 'vs/base/common/strings';
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, 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 { IConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
......@@ -247,6 +248,8 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
private filteredMatchesRenderer: FilteredMatchesRenderer;
private hiddenAreasRenderer: HiddenAreasRenderer;
private editSettingActionRenderer: EditSettingRenderer;
private mostRelevantMatchesRenderer: MostRelevantMatchesRenderer;
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;
......@@ -270,9 +273,14 @@ 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.mostRelevantMatchesRenderer = this._register(instantiationService.createInstance(MostRelevantMatchesRenderer, editor));
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.mostRelevantMatchesRenderer];
this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, hiddenAreasProviders));
this._register(this.settingsGroupTitleRenderer.onHiddenAreasChanged(() => this.hiddenAreasRenderer.render()));
}
......@@ -289,6 +297,8 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
public render() {
this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups);
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel);
this.mostRelevantMatchesRenderer.render(null);
this.feedbackWidgetRenderer.render(null);
this.hiddenAreasRenderer.render();
this.settingHighlighter.clear(true);
this.settingsGroupTitleRenderer.showGroup(1);
......@@ -297,20 +307,31 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR
public filterPreferences(filterResult: IFilterResult): void {
this.filterResult = filterResult;
if (!filterResult) {
if (filterResult) {
if (filterResult.scores) {
this.filteredMatchesRenderer.render(null);
this.settingsGroupTitleRenderer.render(null);
} else {
this.filteredMatchesRenderer.render(filterResult);
this.settingsGroupTitleRenderer.render(filterResult.filteredGroups);
}
this.mostRelevantMatchesRenderer.render(filterResult);
this.feedbackWidgetRenderer.render(filterResult);
this.settingsHeaderRenderer.render(filterResult.filteredGroups);
this.settingHighlighter.clear(true);
this.editSettingActionRenderer.render(filterResult.filteredGroups, this._associatedPreferencesModel);
} else {
this.settingHighlighter.clear(true);
this.filteredMatchesRenderer.render(null);
this.mostRelevantMatchesRenderer.render(null);
this.feedbackWidgetRenderer.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);
this.settingsGroupTitleRenderer.render(filterResult.filteredGroups);
this.settingHighlighter.clear(true);
this.editSettingActionRenderer.render(filterResult.filteredGroups, this._associatedPreferencesModel);
}
this.hiddenAreasRenderer.render();
}
......@@ -377,6 +398,8 @@ 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
return [
{
startLineNumber: 1,
......@@ -384,6 +407,12 @@ export class StaticContentHidingRenderer extends Disposable implements HiddenAre
endLineNumber: 2,
endColumn: model.getLineMaxColumn(2)
},
{
startLineNumber: DefaultSettingsEditorModel.MOST_RELEVANT_SECTION_LENGTH,
startColumn: model.getLineMinColumn(DefaultSettingsEditorModel.MOST_RELEVANT_SECTION_LENGTH),
endLineNumber: DefaultSettingsEditorModel.MOST_RELEVANT_SECTION_LENGTH + 3,
endColumn: model.getLineMaxColumn(DefaultSettingsEditorModel.MOST_RELEVANT_SECTION_LENGTH + 3)
},
{
startLineNumber: this.settingsGroups[0].range.endLineNumber + 1,
startColumn: model.getLineMinColumn(this.settingsGroups[0].range.endLineNumber + 1),
......@@ -446,6 +475,10 @@ 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()) {
......@@ -531,6 +564,195 @@ export class HiddenAreasRenderer extends Disposable {
}
}
export class MostRelevantMatchesRenderer extends Disposable implements HiddenAreasProvider {
private static settingsInsertStart = 4;
private static settingsInsertEnd = DefaultSettingsEditorModel.MOST_RELEVANT_SECTION_LENGTH - 1;
private static emptyLines = MostRelevantMatchesRenderer.settingsInsertEnd - MostRelevantMatchesRenderer.settingsInsertStart + 1;
private static bunchOfNewlines = strings.repeat('\n', MostRelevantMatchesRenderer.emptyLines);
private static editId = 'mostRelevantMatchesRenderer';
public hiddenAreas: IRange[] = [];
constructor(private editor: ICodeEditor,
@IInstantiationService private instantiationService: IInstantiationService
) {
super();
}
public render(result: IFilterResult): void {
this.hiddenAreas = [];
if (result && result.matches.length && result.scores) {
const settingsTextEndLine = this.renderResults(result);
this.hiddenAreas = [{
startLineNumber: settingsTextEndLine + 1,
startColumn: 0,
endLineNumber: this.editor.getModel().getLineCount(),
endColumn: 0
}];
} else {
this.renderSearchResultsSection();
this.hiddenAreas = [{
startLineNumber: MostRelevantMatchesRenderer.settingsInsertStart,
startColumn: 0,
endLineNumber: MostRelevantMatchesRenderer.settingsInsertEnd,
endColumn: 0
}];
}
}
private renderResults(result: IFilterResult): number {
this.hiddenAreas = [];
this.editor.updateOptions({ readOnly: false });
const relevantRanges = this.getOrderedSettingRanges(result.filteredGroups, result.allGroups, result.scores, this.editor.getModel());
let totalLines = 0;
const settingsValue = relevantRanges.map(visibleRange => {
const settingLines = (visibleRange.endLineNumber - visibleRange.startLineNumber) + 1;
if (totalLines + settingLines <= MostRelevantMatchesRenderer.emptyLines) {
totalLines += settingLines;
const value = this.editor.getModel().getValueInRange(visibleRange);
return value.replace(/([^,])\n$/, '$1,\n'); // ensure ends in ','
} else {
// Skip lines that push the total length past 50
return null;
}
})
.filter(line => !!line)
.join('\n');
const settingsTextStartLine = MostRelevantMatchesRenderer.settingsInsertStart;
const settingsTextEndLine = settingsTextStartLine + totalLines - 1;
this.editor.executeEdits(MostRelevantMatchesRenderer.editId, [{
text: settingsValue,
forceMoveMarkers: false,
range: new Range(settingsTextStartLine, 0, settingsTextEndLine, 0),
identifier: { major: 1, minor: 0 }
}]);
this.editor.updateOptions({ readOnly: true });
return settingsTextEndLine;
}
private renderSearchResultsSection(): void {
this.editor.updateOptions({ readOnly: false });
this.editor.executeEdits(MostRelevantMatchesRenderer.editId, [{
text: MostRelevantMatchesRenderer.bunchOfNewlines,
forceMoveMarkers: false,
range: new Range(MostRelevantMatchesRenderer.settingsInsertStart, 0, MostRelevantMatchesRenderer.settingsInsertEnd + 1, 0),
identifier: { major: 1, minor: 0 }
}]);
this.editor.updateOptions({ readOnly: true });
}
private getOrderedSettingRanges(filteredGroups: ISettingsGroup[], allSettingsGroups: ISettingsGroup[], scores: any, model: editorCommon.IModel): IRange[] {
const matchingRanges: { range: IRange, name: string }[] = [];
for (const group of allSettingsGroups) {
const filteredGroup = filteredGroups.filter(g => g.title === group.title)[0];
if (filteredGroup) {
for (const section of group.sections) {
for (const setting of section.settings) {
if (this.containsLine(setting.range.startLineNumber, filteredGroup)) {
matchingRanges.push({
name: setting.key,
range: this.createCompleteRange(setting.range, model)
});
}
}
}
}
}
return matchingRanges
.sort((a, b) => scores[b.name] - scores[a.name])
.map(r => r.range);
}
private containsLine(lineNumber: number, settingsGroup: ISettingsGroup): boolean {
if (settingsGroup.titleRange && lineNumber >= settingsGroup.titleRange.startLineNumber && lineNumber <= settingsGroup.titleRange.endLineNumber) {
return true;
}
for (const section of settingsGroup.sections) {
if (section.titleRange && lineNumber >= section.titleRange.startLineNumber && lineNumber <= section.titleRange.endLineNumber) {
return true;
}
for (const setting of section.settings) {
if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) {
return true;
}
}
}
return false;
}
private createCompleteRange(range: IRange, model: editorCommon.IModel): IRange {
return {
startLineNumber: range.startLineNumber,
startColumn: model.getLineMinColumn(range.startLineNumber),
endLineNumber: range.endLineNumber,
endColumn: model.getLineMaxColumn(range.endLineNumber)
};
}
}
export class FeedbackWidgetRenderer extends Disposable {
private _feedbackWidget: FloatingClickWidget;
constructor(private editor: ICodeEditor,
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
) {
super();
}
public render(result: IFilterResult): void {
if (result && result.scores) {
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 {
this.editorService.openEditor({ contents: 'test' }, true).then(feedbackEditor => {
const sendFeedbackWidget = this._register(this.instantiationService.createInstance(FloatingClickWidget, feedbackEditor.getControl(), 'Send feedback', null));
this._register(sendFeedbackWidget.onClick(() => this.sendFeedback()));
sendFeedbackWidget.render();
});
}
private sendFeedback(): void {
}
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[] = [];
......
/*---------------------------------------------------------------------------------------------
* 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 { ISettingsEditorModel, IFilterResult, ISetting, ISettingsGroup } 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';
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
};
}
}
export class PreferencesSearchProvider {
private _localProvider: LocalSearchProvider;
private _remoteProvider: RemoteSearchProvider;
constructor(filter: string) {
this._localProvider = new LocalSearchProvider(filter);
this._remoteProvider = new RemoteSearchProvider(filter);
}
filterPreferences(preferencesModel: ISettingsEditorModel): TPromise<IFilterResult> {
return this._remoteProvider.filterPreferences(preferencesModel).then(null, err => {
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));
}
}
class RemoteSearchProvider {
private _filter: string;
private _remoteSearchP: TPromise<{ [key: string]: number }>;
constructor(filter: string) {
this._filter = filter;
this._remoteSearchP = filter ? getSettingsFromBing(filter) : TPromise.wrap({});
}
filterPreferences(preferencesModel: ISettingsEditorModel): TPromise<IFilterResult> {
return this._remoteSearchP.then(settingsSet => {
const settingFilter = (setting: ISetting) => {
if (!!settingsSet[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;
}
};
const result = preferencesModel.filterSettings(this._filter, group => null, settingFilter);
result.scores = settingsSet;
return result;
});
}
}
function getSettingsFromBing(filter: string): TPromise<{ [key: string]: number }> {
const url = prepareUrl(filter);
console.log('fetching: ' + url);
const start = Date.now();
const p = fetch(url, {
headers: {
'User-Agent': 'request',
'Content-Type': 'application/json; charset=utf-8',
'api-key': endpoint.key
}
})
.then(r => r.json())
.then(result => {
console.log('time: ' + (Date.now() - start) / 1000);
const suggestions = (result.value || [])
.map(r => ({
name: r.Setting,
score: r['@search.score']
}));
const suggSet = Object.create(null);
suggestions.forEach(s => {
const name = s.name
.replace(/^"/, '')
.replace(/"$/, '');
suggSet[name] = s.score;
});
return suggSet;
});
return TPromise.as(p as any);
}
const endpoint = {
key: 'F3F22B32DD89DDA74B1935ED0BE6FCBA',
urlBase: 'https://vscodesearch6.search.windows.net/indexes/vscodeindex/docs'
};
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): 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}`;
}
......@@ -59,10 +59,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic
// TODO:@sandy merge these models into editor inputs by extending resource editor model
private defaultPreferencesEditorModels: ResourceMap<TPromise<IPreferencesEditorModel<any>>>;
private defaultPreferencesEditorModelsInUse: ResourceMap<boolean>;
private lastOpenedSettingsInput: PreferencesEditorInput = null;
private _onDispose: Emitter<void> = new Emitter<void>();
private _defaultSettingsUriCounter = 0;
private _defaultResourceSettingsUriCounter = 0;
constructor(
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupService private editorGroupService: IEditorGroupService,
......@@ -84,6 +88,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
) {
super();
this.defaultPreferencesEditorModels = new ResourceMap<TPromise<IPreferencesEditorModel<any>>>();
this.defaultPreferencesEditorModelsInUse = new ResourceMap<boolean>();
this.editorGroupService.onEditorsChanged(() => {
const activeEditorInput = this.editorService.getActiveEditorInput();
if (activeEditorInput instanceof PreferencesEditorInput) {
......@@ -103,8 +108,8 @@ 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 defaultSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/0/settings.json' });
readonly defaultResourceSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/0/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' });
......@@ -129,13 +134,28 @@ export class PreferencesService extends Disposable implements IPreferencesServic
.then(preferencesEditorModel => preferencesEditorModel ? preferencesEditorModel.content : null);
}
disownPreferencesEditorModel(editorModel: IPreferencesEditorModel<any>): void {
const uriStr = editorModel.uri.toString();
if (uriStr === this.defaultSettingsResource.toString()) {
this.defaultPreferencesEditorModelsInUse.set(editorModel.uri, false);
} else {
this.defaultPreferencesEditorModels.delete(editorModel.uri);
editorModel.dispose();
}
}
createPreferencesEditorModel(uri: URI): TPromise<IPreferencesEditorModel<any>> {
// Mark model 0 in use
if (uri.toString() === this.defaultSettingsResource.toString()) {
this.defaultPreferencesEditorModelsInUse.set(uri, true);
}
let promise = this.defaultPreferencesEditorModels.get(uri);
if (promise) {
return promise;
}
if (this.defaultSettingsResource.toString() === uri.toString()) {
if (this.isDefaultSettingsResource(uri)) {
promise = TPromise.join<any>([this.extensionService.onReady(), this.fetchMostCommonlyUsedSettings()])
.then(result => {
const mostCommonSettings = result[1];
......@@ -146,7 +166,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic
return promise;
}
if (this.defaultResourceSettingsResource.toString() === uri.toString()) {
if (this.isDefaultResourceSettingsResource(uri)) {
promise = TPromise.join<any>([this.extensionService.onReady(), this.fetchMostCommonlyUsedSettings()])
.then(result => {
const mostCommonSettings = result[1];
......@@ -273,11 +293,28 @@ 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.FOLDER) {
return this.defaultResourceSettingsResource;
if (this.defaultPreferencesEditorModelsInUse.get(this.defaultSettingsResource)) {
return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultResourceSettingsUriCounter++}/resourceSettings.json` });
} else {
return this.defaultResourceSettingsResource;
}
} else {
if (this.defaultPreferencesEditorModelsInUse.get(this.defaultSettingsResource)) {
return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: `/${this._defaultSettingsUriCounter++}/settings.json` });
} else {
return this.defaultSettingsResource;
}
}
return this.defaultSettingsResource;
}
private getPreferencesEditorInputName(target: ConfigurationTarget, resource: URI): string {
......
......@@ -44,6 +44,7 @@ export interface IFilterResult {
filteredGroups: ISettingsGroup[];
allGroups: ISettingsGroup[];
matches: IRange[];
scores?: { [key: string]: number };
}
export interface IPreferencesEditorModel<T> {
......@@ -53,10 +54,14 @@ export interface IPreferencesEditorModel<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): IFilterResult;
findValueMatches(filter: string, setting: ISetting): IRange[];
}
export interface IKeybindingsEditorModel<T> extends IPreferencesEditorModel<T> {
......@@ -68,13 +73,13 @@ export interface IPreferencesService {
_serviceBrand: any;
defaultSettingsResource: URI;
defaultResourceSettingsResource: URI;
userSettingsResource: URI;
workspaceSettingsResource: URI;
getFolderSettingsResource(resource: URI): URI;
resolveContent(uri: URI): TPromise<string>;
createPreferencesEditorModel<T>(uri: URI): TPromise<IPreferencesEditorModel<T>>;
disownPreferencesEditorModel(editorModel: IPreferencesEditorModel<any>): void;
openGlobalSettings(): TPromise<IEditor>;
openWorkspaceSettings(): TPromise<IEditor>;
......
......@@ -4,22 +4,19 @@
*--------------------------------------------------------------------------------------------*/
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 * as arrays 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 { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
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 { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
......@@ -27,135 +24,15 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { Queue } from 'vs/base/common/async';
import { IFileService } from 'vs/platform/files/common/files';
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
};
}
}
export abstract class AbstractSettingsModel extends EditorModel {
public get groupsTerms(): string[] {
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,
......@@ -175,18 +52,20 @@ export abstract class AbstractSettingsModel extends EditorModel {
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({
......@@ -232,7 +111,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 +149,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 +165,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[] = [];
......@@ -594,6 +473,8 @@ export class WorkspaceConfigModel extends SettingsEditorModel implements ISettin
export class DefaultSettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel {
public static MOST_RELEVANT_SECTION_LENGTH = 100;
private _allSettingsGroups: ISettingsGroup[];
private _content: string;
private _contentByLines: string[];
......@@ -624,8 +505,12 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
return this.settingsGroups[0];
}
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 [];
}
public getPreference(key: string): ISetting {
......@@ -775,6 +660,10 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
private toContent(mostCommonlyUsed: ISettingsGroup, settingsGroups: ISettingsGroup[]): string {
this._contentByLines = [];
this._contentByLines.push('[');
this._contentByLines.push('{');
this._contentByLines.push(...arrays.fill(DefaultSettingsEditorModel.MOST_RELEVANT_SECTION_LENGTH - 3, () => ''));
this._contentByLines.push('}');
this._contentByLines.push(',');
this.pushGroups([mostCommonlyUsed]);
this._contentByLines.push(',');
this.pushGroups(settingsGroups);
......@@ -878,12 +767,9 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
}
}
protected findValueMatches(filter: string, setting: ISetting): IRange[] {
return [];
}
public dispose(): void {
// Not disposable
super.dispose();
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册