Adopt settings and keybindings to use user data filesystem pro… (#76379)
Adopt settings and keybindings to use user data filesystem provider
This commit is contained in:
commit
71bd9c68e6
|
@ -337,11 +337,13 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH
|
|||
console[severity].apply(console, ...messages);
|
||||
}
|
||||
|
||||
async showItemInFolder(path: URI): Promise<void> {
|
||||
async showItemInFolder(resource: URI): Promise<void> {
|
||||
this.logService.trace('windowsService#showItemInFolder');
|
||||
|
||||
if (path.scheme === Schemas.file) {
|
||||
shell.showItemInFolder(path.fsPath);
|
||||
if (resource.scheme === Schemas.file) {
|
||||
shell.showItemInFolder(resource.fsPath);
|
||||
} else if (resource.scheme === Schemas.userData) {
|
||||
shell.showItemInFolder(resource.path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -200,6 +200,7 @@ const copyRelativePathCommand = {
|
|||
appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste');
|
||||
appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste');
|
||||
appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file));
|
||||
appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.isEqualTo(Schemas.userData)));
|
||||
appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFileSystemResource);
|
||||
|
||||
function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr, group?: string): void {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
|||
import * as errors from 'vs/base/common/errors';
|
||||
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
|
||||
import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files';
|
||||
import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
|
||||
import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels';
|
||||
import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES, ConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
|
@ -24,11 +24,51 @@ import { IConfigurationModel } from 'vs/platform/configuration/common/configurat
|
|||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
|
||||
export class UserConfiguration extends Disposable {
|
||||
|
||||
private readonly parser: ConfigurationModelParser;
|
||||
private readonly reloadConfigurationScheduler: RunOnceScheduler;
|
||||
protected readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
|
||||
readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
|
||||
|
||||
constructor(
|
||||
private readonly userSettingsResource: URI,
|
||||
private readonly scopes: ConfigurationScope[] | undefined,
|
||||
private readonly fileService: IFileService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes);
|
||||
this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50));
|
||||
this._register(this.fileService.watch(this.userSettingsResource));
|
||||
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this.reloadConfigurationScheduler.schedule()));
|
||||
}
|
||||
|
||||
async initialize(): Promise<ConfigurationModel> {
|
||||
return this.reload();
|
||||
}
|
||||
|
||||
async reload(): Promise<ConfigurationModel> {
|
||||
try {
|
||||
const content = await this.fileService.readFile(this.userSettingsResource);
|
||||
this.parser.parseContent(content.value.toString() || '{}');
|
||||
return this.parser.configurationModel;
|
||||
} catch (e) {
|
||||
return new ConfigurationModel();
|
||||
}
|
||||
}
|
||||
|
||||
reprocess(): ConfigurationModel {
|
||||
this.parser.parse();
|
||||
return this.parser.configurationModel;
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteUserConfiguration extends Disposable {
|
||||
|
||||
private readonly _cachedConfiguration: CachedRemoteUserConfiguration;
|
||||
private readonly _configurationFileService: ConfigurationFileService;
|
||||
private _userConfiguration: UserConfiguration | CachedRemoteUserConfiguration;
|
||||
private _userConfiguration: FileServiceBasedRemoteUserConfiguration | CachedRemoteUserConfiguration;
|
||||
private _userConfigurationInitializationPromise: Promise<ConfigurationModel> | null = null;
|
||||
|
||||
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
|
||||
|
@ -45,7 +85,7 @@ export class RemoteUserConfiguration extends Disposable {
|
|||
this._userConfiguration = this._cachedConfiguration = new CachedRemoteUserConfiguration(remoteAuthority, configurationCache);
|
||||
remoteAgentService.getEnvironment().then(async environment => {
|
||||
if (environment) {
|
||||
const userConfiguration = this._register(new UserConfiguration(environment.settingsPath, REMOTE_MACHINE_SCOPES, this._configurationFileService));
|
||||
const userConfiguration = this._register(new FileServiceBasedRemoteUserConfiguration(environment.settingsPath, REMOTE_MACHINE_SCOPES, this._configurationFileService));
|
||||
this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));
|
||||
this._userConfigurationInitializationPromise = userConfiguration.initialize();
|
||||
const configurationModel = await this._userConfigurationInitializationPromise;
|
||||
|
@ -57,7 +97,7 @@ export class RemoteUserConfiguration extends Disposable {
|
|||
}
|
||||
|
||||
async initialize(): Promise<ConfigurationModel> {
|
||||
if (this._userConfiguration instanceof UserConfiguration) {
|
||||
if (this._userConfiguration instanceof FileServiceBasedRemoteUserConfiguration) {
|
||||
return this._userConfiguration.initialize();
|
||||
}
|
||||
|
||||
|
@ -90,7 +130,7 @@ export class RemoteUserConfiguration extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
export class UserConfiguration extends Disposable {
|
||||
class FileServiceBasedRemoteUserConfiguration extends Disposable {
|
||||
|
||||
private readonly parser: ConfigurationModelParser;
|
||||
private readonly reloadConfigurationScheduler: RunOnceScheduler;
|
||||
|
|
|
@ -81,7 +81,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
|
|||
this.configurationFileService = new ConfigurationFileService(fileService);
|
||||
this._configuration = new Configuration(this.defaultConfiguration, new ConfigurationModel(), new ConfigurationModel(), new ConfigurationModel(), new ResourceMap(), new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), this.workspace);
|
||||
this.cachedFolderConfigs = new ResourceMap<FolderConfiguration>();
|
||||
this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, this.configurationFileService));
|
||||
this.localUserConfiguration = this._register(new UserConfiguration(environmentService.settingsResource, remoteAuthority ? LOCAL_MACHINE_SCOPES : undefined, fileService));
|
||||
this._register(this.localUserConfiguration.onDidChangeConfiguration(userConfiguration => this.onLocalUserConfigurationChanged(userConfiguration)));
|
||||
if (remoteAuthority) {
|
||||
this.remoteUserConfiguration = this._register(new RemoteUserConfiguration(remoteAuthority, configurationCache, this.configurationFileService, remoteAgentService));
|
||||
|
|
|
@ -71,9 +71,9 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
|
|||
this.configuration.remoteAuthority = configuration.remoteAuthority;
|
||||
|
||||
if (remoteUserDataUri) {
|
||||
this.appSettingsHome = remoteUserDataUri || URI.file('/User').with({ scheme: Schemas.userData });
|
||||
this.settingsResource = joinPath(this.appSettingsHome, 'settings.json');
|
||||
this.keybindingsResource = joinPath(this.appSettingsHome, 'keybindings.json');
|
||||
this.appSettingsHome = remoteUserDataUri;
|
||||
this.settingsResource = joinPath(this.appSettingsHome, 'settings.json').with({ scheme: Schemas.userData });
|
||||
this.keybindingsResource = joinPath(this.appSettingsHome, 'keybindings.json').with({ scheme: Schemas.userData });
|
||||
} else {
|
||||
const appSettingsHome = URI.file('/User').with({ scheme: Schemas.userData });
|
||||
this.settingsResource = joinPath(appSettingsHome, 'settings.json');
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService {
|
||||
|
||||
|
@ -21,4 +25,10 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I
|
|||
get configuration(): IWindowConfiguration {
|
||||
return this._configuration;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get settingsResource(): URI { return joinPath(this.appSettingsHome, 'settings.json').with({ scheme: Schemas.userData }); }
|
||||
|
||||
@memoize
|
||||
get keybindingsResource(): URI { return joinPath(this.appSettingsHome, 'keybindings.json').with({ scheme: Schemas.userData }); }
|
||||
}
|
||||
|
|
|
@ -36,11 +36,10 @@ import { MenuRegistry } from 'vs/platform/actions/common/actions';
|
|||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
// tslint:disable-next-line: import-patterns
|
||||
import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint';
|
||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } 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 { IFileService } from 'vs/platform/files/common/files';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapInfo';
|
||||
|
@ -560,12 +559,11 @@ 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;
|
||||
private readonly reloadConfigurationScheduler: RunOnceScheduler;
|
||||
|
||||
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
constructor(
|
||||
private readonly keybindingsResource: URI,
|
||||
|
@ -573,40 +571,16 @@ class UserKeybindings extends Disposable {
|
|||
) {
|
||||
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;
|
||||
this._register(this.fileService.watch(this.keybindingsResource));
|
||||
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.keybindingsResource))(() => this.reloadConfigurationScheduler.schedule()));
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
const exists = await this.fileService.exists(this.keybindingsResource);
|
||||
this.onResourceExists(exists);
|
||||
await this.reload();
|
||||
}
|
||||
|
||||
|
@ -621,39 +595,6 @@ class UserKeybindings extends Disposable {
|
|||
}
|
||||
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';
|
||||
|
|
|
@ -10,6 +10,7 @@ import { IFileService, FileChangesEvent } from 'vs/platform/files/common/files';
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export class FileUserDataProvider extends Disposable implements IUserDataProvider {
|
||||
|
||||
|
@ -28,17 +29,17 @@ export class FileUserDataProvider extends Disposable implements IUserDataProvide
|
|||
}
|
||||
|
||||
private handleFileChanges(event: FileChangesEvent): void {
|
||||
const changedKeys: string[] = [];
|
||||
const changedPaths: string[] = [];
|
||||
for (const change of event.changes) {
|
||||
if (change.resource.scheme === this.userDataHome.scheme) {
|
||||
const key = this.toKey(change.resource);
|
||||
if (key) {
|
||||
changedKeys.push(key);
|
||||
const path = this.toPath(change.resource);
|
||||
if (path) {
|
||||
changedPaths.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changedKeys.length) {
|
||||
this._onDidChangeFile.fire(changedKeys);
|
||||
if (changedPaths.length) {
|
||||
this._onDidChangeFile.fire(changedPaths);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,18 +63,20 @@ export class FileUserDataProvider extends Disposable implements IUserDataProvide
|
|||
|
||||
async listFiles(path: string): Promise<string[]> {
|
||||
const result = await this.fileService.resolve(this.toResource(path));
|
||||
return result.children ? result.children.map(c => this.toKey(c.resource)!) : [];
|
||||
return result.children ? result.children.map(c => this.toPath(c.resource)!) : [];
|
||||
}
|
||||
|
||||
deleteFile(path: string): Promise<void> {
|
||||
return this.fileService.del(this.toResource(path));
|
||||
}
|
||||
|
||||
private toResource(key: string): URI {
|
||||
return resources.joinPath(this.userDataHome, ...key.split('/'));
|
||||
private toResource(path: string): URI {
|
||||
return resources.joinPath(this.userDataHome, path);
|
||||
}
|
||||
|
||||
private toKey(resource: URI): string | undefined {
|
||||
return resources.relativePath(this.userDataHome, resource);
|
||||
private toPath(resource: URI): string | undefined {
|
||||
const resourcePath = resource.toString();
|
||||
const userDataHomePath = this.userDataHome.toString();
|
||||
return startsWith(resourcePath, userDataHomePath) ? resourcePath.substr(userDataHomePath.length + 1) : undefined;
|
||||
}
|
||||
}
|
|
@ -8,8 +8,8 @@ import { FileSystemProviderCapabilities, FileWriteOptions, IStat, FileType, File
|
|||
import { IUserDataProvider } from 'vs/workbench/services/userData/common/userData';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export class UserDataFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
|
||||
|
||||
|
@ -85,7 +85,9 @@ export class UserDataFileSystemProvider extends Disposable implements IFileSyste
|
|||
}
|
||||
|
||||
private toPath(resource: URI): string | undefined {
|
||||
return resources.relativePath(this.userDataHome, resource);
|
||||
const resourcePath = resource.toString();
|
||||
const userDataHomePath = this.userDataHome.toString();
|
||||
return startsWith(resourcePath, userDataHomePath) ? resourcePath.substr(userDataHomePath.length + 1) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,8 +107,8 @@ class UserDataChangesEvent {
|
|||
return this._pathsTree;
|
||||
}
|
||||
|
||||
contains(keyOrSegment: string): boolean {
|
||||
return this.pathsTree.findSubstr(keyOrSegment) !== undefined;
|
||||
contains(pathOrSegment: string): boolean {
|
||||
return this.pathsTree.findSubstr(pathOrSegment) !== undefined;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue