mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 13:29:44 +00:00
#75079 Use fileservice for listening to keybindings file changes
This commit is contained in:
parent
3c1b39ae6b
commit
a0184cc541
|
@ -8,13 +8,11 @@ import * as nativeKeymap from 'native-keymap';
|
|||
import { release } from 'os';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingParser } from 'vs/base/common/keybindingParser';
|
||||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { ConfigWatcher } from 'vs/base/node/config';
|
||||
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
|
@ -41,6 +39,13 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
|||
import { MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { dirname, isEqual } from 'vs/base/common/resources';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
|
||||
export class KeyboardMapperFactory {
|
||||
public static readonly INSTANCE = new KeyboardMapperFactory();
|
||||
|
@ -267,8 +272,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
|||
|
||||
private _keyboardMapper: IKeyboardMapper;
|
||||
private _cachedResolver: KeybindingResolver | null;
|
||||
private _firstTimeComputingResolver: boolean;
|
||||
private userKeybindings: ConfigWatcher<IUserFriendlyKeybinding[]>;
|
||||
private userKeybindings: UserKeybindings;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
|
@ -278,7 +282,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
|||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IExtensionService extensionService: IExtensionService
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IFileService fileService: IFileService
|
||||
) {
|
||||
super(contextKeyService, commandService, telemetryService, notificationService);
|
||||
|
||||
|
@ -303,9 +308,27 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
|||
});
|
||||
|
||||
this._cachedResolver = null;
|
||||
this._firstTimeComputingResolver = true;
|
||||
|
||||
this.userKeybindings = this._register(new ConfigWatcher(environmentService.keybindingsResource.fsPath, { defaultConfig: [], onError: error => onUnexpectedError(error) }));
|
||||
this.userKeybindings = this._register(new UserKeybindings(environmentService.keybindingsResource, fileService));
|
||||
this.userKeybindings.initialize().then(() => {
|
||||
if (this.userKeybindings.keybindings.length) {
|
||||
this.updateResolver({ source: KeybindingSource.User });
|
||||
}
|
||||
});
|
||||
this._register(this.userKeybindings.onDidChange(() => {
|
||||
/* __GDPR__
|
||||
"customKeybindingsChanged" : {
|
||||
"keyCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('customKeybindingsChanged', {
|
||||
keyCount: this.userKeybindings.keybindings.length
|
||||
});
|
||||
this.updateResolver({
|
||||
source: KeybindingSource.User,
|
||||
keybindings: this.userKeybindings.keybindings
|
||||
});
|
||||
}));
|
||||
|
||||
keybindingsExtPoint.setHandler((extensions) => {
|
||||
|
||||
|
@ -321,11 +344,6 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
|||
updateSchema();
|
||||
this._register(extensionService.onDidRegisterExtensions(() => updateSchema()));
|
||||
|
||||
this._register(this.userKeybindings.onDidUpdateConfiguration(event => this.updateResolver({
|
||||
source: KeybindingSource.User,
|
||||
keybindings: event.config
|
||||
})));
|
||||
|
||||
this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
let keyEvent = new StandardKeyboardEvent(e);
|
||||
let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
|
||||
|
@ -353,18 +371,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
|||
return `Layout info:\n${layoutInfo}\n${mapperInfo}\n\nRaw mapping:\n${rawMapping}`;
|
||||
}
|
||||
|
||||
private _safeGetConfig(): IUserFriendlyKeybinding[] {
|
||||
let rawConfig = this.userKeybindings.getConfig();
|
||||
if (Array.isArray(rawConfig)) {
|
||||
return rawConfig;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public customKeybindingsCount(): number {
|
||||
let userKeybindings = this._safeGetConfig();
|
||||
|
||||
return userKeybindings.length;
|
||||
return this.userKeybindings.keybindings.length;
|
||||
}
|
||||
|
||||
private updateResolver(event: IKeybindingEvent): void {
|
||||
|
@ -375,9 +383,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
|||
protected _getResolver(): KeybindingResolver {
|
||||
if (!this._cachedResolver) {
|
||||
const defaults = this._resolveKeybindingItems(KeybindingsRegistry.getDefaultKeybindings(), true);
|
||||
const overrides = this._resolveUserKeybindingItems(this._getExtraKeybindings(this._firstTimeComputingResolver), false);
|
||||
const overrides = this._resolveUserKeybindingItems(this.userKeybindings.keybindings.map((k) => KeybindingIO.readUserKeybindingItem(k)), false);
|
||||
this._cachedResolver = new KeybindingResolver(defaults, overrides);
|
||||
this._firstTimeComputingResolver = false;
|
||||
}
|
||||
return this._cachedResolver;
|
||||
}
|
||||
|
@ -427,24 +434,6 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
|||
return result;
|
||||
}
|
||||
|
||||
private _getExtraKeybindings(isFirstTime: boolean): IUserKeybindingItem[] {
|
||||
let extraUserKeybindings: IUserFriendlyKeybinding[] = this._safeGetConfig();
|
||||
if (!isFirstTime) {
|
||||
let cnt = extraUserKeybindings.length;
|
||||
|
||||
/* __GDPR__
|
||||
"customKeybindingsChanged" : {
|
||||
"keyCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('customKeybindingsChanged', {
|
||||
keyCount: cnt
|
||||
});
|
||||
}
|
||||
|
||||
return extraUserKeybindings.map((k) => KeybindingIO.readUserKeybindingItem(k));
|
||||
}
|
||||
|
||||
public resolveKeybinding(kb: Keybinding): ResolvedKeybinding[] {
|
||||
return this._keyboardMapper.resolveKeybinding(kb);
|
||||
}
|
||||
|
@ -585,6 +574,105 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
|||
}
|
||||
}
|
||||
|
||||
class UserKeybindings extends Disposable {
|
||||
|
||||
private _keybindings: IUserFriendlyKeybinding[] = [];
|
||||
get keybindings(): IUserFriendlyKeybinding[] { return this._keybindings; }
|
||||
private readonly reloadConfigurationScheduler: RunOnceScheduler;
|
||||
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
private fileWatcherDisposable: IDisposable = Disposable.None;
|
||||
private directoryWatcherDisposable: IDisposable = Disposable.None;
|
||||
|
||||
constructor(
|
||||
private readonly keybindingsResource: URI,
|
||||
private readonly fileService: IFileService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(fileService.onFileChanges(e => this.handleFileEvents(e)));
|
||||
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(changed => {
|
||||
if (changed) {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}), 50));
|
||||
this._register(toDisposable(() => {
|
||||
this.stopWatchingResource();
|
||||
this.stopWatchingDirectory();
|
||||
}));
|
||||
}
|
||||
|
||||
private watchResource(): void {
|
||||
this.fileWatcherDisposable = this.fileService.watch(this.keybindingsResource);
|
||||
}
|
||||
|
||||
private stopWatchingResource(): void {
|
||||
this.fileWatcherDisposable.dispose();
|
||||
this.fileWatcherDisposable = Disposable.None;
|
||||
}
|
||||
|
||||
private watchDirectory(): void {
|
||||
const directory = dirname(this.keybindingsResource);
|
||||
this.directoryWatcherDisposable = this.fileService.watch(directory);
|
||||
}
|
||||
|
||||
private stopWatchingDirectory(): void {
|
||||
this.directoryWatcherDisposable.dispose();
|
||||
this.directoryWatcherDisposable = Disposable.None;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
const exists = await this.fileService.exists(this.keybindingsResource);
|
||||
this.onResourceExists(exists);
|
||||
await this.reload();
|
||||
}
|
||||
|
||||
private async reload(): Promise<boolean> {
|
||||
const existing = this._keybindings;
|
||||
try {
|
||||
const content = await this.fileService.readFile(this.keybindingsResource);
|
||||
this._keybindings = parse(content.value.toString());
|
||||
} catch (e) {
|
||||
this._keybindings = [];
|
||||
}
|
||||
return existing ? !objects.equals(existing, this._keybindings) : true;
|
||||
}
|
||||
|
||||
private async handleFileEvents(event: FileChangesEvent): Promise<void> {
|
||||
const events = event.changes;
|
||||
|
||||
let affectedByChanges = false;
|
||||
|
||||
// Find changes that affect the resource
|
||||
for (const event of events) {
|
||||
affectedByChanges = isEqual(this.keybindingsResource, event.resource);
|
||||
if (affectedByChanges) {
|
||||
if (event.type === FileChangeType.ADDED) {
|
||||
this.onResourceExists(true);
|
||||
} else if (event.type === FileChangeType.DELETED) {
|
||||
this.onResourceExists(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (affectedByChanges) {
|
||||
this.reloadConfigurationScheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
private onResourceExists(exists: boolean): void {
|
||||
if (exists) {
|
||||
this.stopWatchingDirectory();
|
||||
this.watchResource();
|
||||
} else {
|
||||
this.stopWatchingResource();
|
||||
this.watchDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let schemaId = 'vscode://schemas/keybindings';
|
||||
let commandsSchemas: IJSONSchema[] = [];
|
||||
let commandsEnum: string[] = [];
|
||||
|
|
Loading…
Reference in a new issue