浏览代码

Consolidates settings webview with base

main
Eric Amodio 1年前
父节点
当前提交
a065cd5f87
共有 4 个文件被更改,包括 260 次插入256 次删除
  1. +4
    -2
      src/webviews/apps/settings/settings.ts
  2. +8
    -1
      src/webviews/protocol.ts
  3. +248
    -20
      src/webviews/settings/settingsWebview.ts
  4. +0
    -233
      src/webviews/webviewWithConfigBase.ts

+ 4
- 2
src/webviews/apps/settings/settings.ts 查看文件

@ -1,7 +1,7 @@
/*global document IntersectionObserver*/
import './settings.scss';
import type { AutolinkReference } from '../../../config';
import type { IpcMessage } from '../../protocol';
import type { IpcMessage, UpdateConfigurationParams } from '../../protocol';
import {
DidChangeConfigurationNotificationType,
DidGenerateConfigurationPreviewNotificationType,
@ -163,7 +163,9 @@ export class SettingsApp extends App {
private applyChanges() {
this.sendCommand(UpdateConfigurationCommandType, {
changes: { ...this._changes },
removes: Object.keys(this._changes).filter(k => this._changes[k] === undefined),
removes: Object.keys(this._changes).filter(
(k): k is UpdateConfigurationParams['removes'][0] => this._changes[k] === undefined,
),
scope: this.getSettingsScope(),
});

+ 8
- 1
src/webviews/protocol.ts 查看文件

@ -64,7 +64,7 @@ export interface UpdateConfigurationParams {
changes: {
[key in ConfigPath | CustomConfigPath]?: ConfigPathValue<ConfigPath> | CustomConfigPathValue<CustomConfigPath>;
};
removes: string[];
removes: (keyof { [key in ConfigPath | CustomConfigPath]?: ConfigPathValue<ConfigPath> })[];
scope?: 'user' | 'workspace';
uri?: string;
}
@ -113,3 +113,10 @@ const customConfigKeys: readonly CustomConfigPath[] = [
export function isCustomConfigKey(key: string): key is CustomConfigPath {
return customConfigKeys.includes(key as CustomConfigPath);
}
export function assertsConfigKeyValue<T extends ConfigPath>(
key: T,
value: unknown,
): asserts value is ConfigPathValue<T> {
// Noop
}

+ 248
- 20
src/webviews/settings/settingsWebview.ts 查看文件

@ -1,14 +1,59 @@
import type { ViewColumn } from 'vscode';
import { workspace } from 'vscode';
import type { ConfigurationChangeEvent, Disposable, ViewColumn } from 'vscode';
import { ConfigurationTarget, workspace } from 'vscode';
import type { CoreConfiguration } from '../../constants';
import { extensionPrefix } from '../../constants';
import type { Container } from '../../container';
import { CommitFormatter } from '../../git/formatters/commitFormatter';
import { GitCommit, GitCommitIdentity } from '../../git/models/commit';
import { GitFileChange, GitFileIndexStatus } from '../../git/models/file';
import { PullRequest, PullRequestState } from '../../git/models/pullRequest';
import type { ConfigPath } from '../../system/configuration';
import { configuration } from '../../system/configuration';
import { DidOpenAnchorNotificationType } from '../protocol';
import type { WebviewProvider } from '../webviewController';
import { WebviewProviderWithConfigBase } from '../webviewWithConfigBase';
import { map } from '../../system/iterable';
import { Logger } from '../../system/logger';
import type { CustomConfigPath, IpcMessage } from '../protocol';
import {
assertsConfigKeyValue,
DidChangeConfigurationNotificationType,
DidGenerateConfigurationPreviewNotificationType,
DidOpenAnchorNotificationType,
GenerateConfigurationPreviewCommandType,
isCustomConfigKey,
onIpc,
UpdateConfigurationCommandType,
} from '../protocol';
import type { WebviewController, WebviewProvider } from '../webviewController';
import type { State } from './protocol';
export class SettingsWebviewProvider extends WebviewProviderWithConfigBase<State> implements WebviewProvider<State> {
export class SettingsWebviewProvider implements WebviewProvider<State> {
private readonly _disposable: Disposable;
private _pendingJumpToAnchor: string | undefined;
constructor(protected readonly container: Container, protected readonly host: WebviewController<State>) {
this._disposable = configuration.onDidChangeAny(this.onAnyConfigurationChanged, this);
}
dispose() {
this._disposable.dispose();
}
includeBootstrap(): State {
const scopes: ['user' | 'workspace', string][] = [['user', 'User']];
if (workspace.workspaceFolders?.length) {
scopes.push(['workspace', 'Workspace']);
}
return {
timestamp: Date.now(),
version: this.container.version,
// Make sure to get the raw config, not from the container which has the modes mixed in
config: configuration.getAll(true),
customSettings: this.getCustomSettings(),
scope: 'user',
scopes: scopes,
};
}
onShowing?(
loading: boolean,
_options: { column?: ViewColumn; preserveFocus?: boolean },
@ -33,21 +78,11 @@ export class SettingsWebviewProvider extends WebviewProviderWithConfigBase
return true;
}
includeBootstrap(): State {
const scopes: ['user' | 'workspace', string][] = [['user', 'User']];
if (workspace.workspaceFolders?.length) {
scopes.push(['workspace', 'Workspace']);
onActiveChanged(active: boolean): void {
// Anytime the webview becomes active, make sure it has the most up-to-date config
if (active) {
void this.notifyDidChangeConfiguration();
}
return {
timestamp: Date.now(),
version: this.container.version,
// Make sure to get the raw config, not from the container which has the modes mixed in
config: configuration.getAll(true),
customSettings: this.getCustomSettings(),
scope: 'user',
scopes: scopes,
};
}
onReady() {
@ -58,4 +93,197 @@ export class SettingsWebviewProvider extends WebviewProviderWithConfigBase
void this.host.notify(DidOpenAnchorNotificationType, { anchor: anchor, scrollBehavior: 'auto' });
}
}
onMessageReceived(e: IpcMessage): void {
if (e == null) return;
switch (e.method) {
case UpdateConfigurationCommandType.method:
Logger.debug(`Webview(${this.host.id}).onMessageReceived: method=${e.method}`);
onIpc(UpdateConfigurationCommandType, e, async params => {
const target =
params.scope === 'workspace' ? ConfigurationTarget.Workspace : ConfigurationTarget.Global;
let key: keyof typeof params.changes;
for (key in params.changes) {
let value = params.changes[key];
if (isCustomConfigKey(key)) {
const customSetting = this.customSettings.get(key);
if (customSetting != null) {
if (typeof value === 'boolean') {
await customSetting.update(value);
} else {
debugger;
}
}
continue;
}
assertsConfigKeyValue(key, value);
const inspect = configuration.inspect(key)!;
if (value != null) {
if (params.scope === 'workspace') {
if (value === inspect.workspaceValue) continue;
} else {
if (value === inspect.globalValue && value !== inspect.defaultValue) continue;
if (value === inspect.defaultValue) {
value = undefined;
}
}
}
await configuration.update(key, value, target);
}
for (const key of params.removes) {
await configuration.update(key as ConfigPath, undefined, target);
}
});
break;
case GenerateConfigurationPreviewCommandType.method:
Logger.debug(`Webview(${this.host.id}).onMessageReceived: method=${e.method}`);
onIpc(GenerateConfigurationPreviewCommandType, e, async params => {
switch (params.type) {
case 'commit':
case 'commit-uncommitted': {
const commit = new GitCommit(
this.container,
'~/code/eamodio/vscode-gitlens-demo',
'fe26af408293cba5b4bfd77306e1ac9ff7ccaef8',
new GitCommitIdentity('You', 'eamodio@gmail.com', new Date('2016-11-12T20:41:00.000Z')),
new GitCommitIdentity('You', 'eamodio@gmail.com', new Date('2020-11-01T06:57:21.000Z')),
params.type === 'commit-uncommitted' ? 'Uncommitted changes' : 'Supercharged',
['3ac1d3f51d7cf5f438cc69f25f6740536ad80fef'],
params.type === 'commit-uncommitted' ? 'Uncommitted changes' : 'Supercharged',
new GitFileChange(
'~/code/eamodio/vscode-gitlens-demo',
'code.ts',
GitFileIndexStatus.Modified,
),
undefined,
[],
);
let includePullRequest = false;
switch (params.key) {
case configuration.name('currentLine.format'):
includePullRequest = configuration.get('currentLine.pullRequests.enabled');
break;
case configuration.name('statusBar.format'):
includePullRequest = configuration.get('statusBar.pullRequests.enabled');
break;
}
let pr: PullRequest | undefined;
if (includePullRequest) {
pr = new PullRequest(
{ id: 'github', name: 'GitHub', domain: 'github.com', icon: 'github' },
{
name: 'Eric Amodio',
avatarUrl: 'https://avatars1.githubusercontent.com/u/641685?s=32&v=4',
url: 'https://github.com/eamodio',
},
'1',
'Supercharged',
'https://github.com/gitkraken/vscode-gitlens/pulls/1',
PullRequestState.Merged,
new Date('Sat, 12 Nov 2016 19:41:00 GMT'),
undefined,
new Date('Sat, 12 Nov 2016 20:41:00 GMT'),
);
}
let preview;
try {
preview = CommitFormatter.fromTemplate(params.format, commit, {
dateFormat: configuration.get('defaultDateFormat'),
pullRequestOrRemote: pr,
messageTruncateAtNewLine: true,
});
} catch {
preview = 'Invalid format';
}
await this.host.notify(
DidGenerateConfigurationPreviewNotificationType,
{ preview: preview },
e.completionId,
);
}
}
});
break;
}
}
private onAnyConfigurationChanged(e: ConfigurationChangeEvent) {
if (!configuration.changedAny(e, extensionPrefix)) {
const notify = configuration.changedAny<CustomSetting['name']>(e, [
...map(this.customSettings.values(), s => s.name),
]);
if (!notify) return;
}
void this.notifyDidChangeConfiguration();
}
private _customSettings: Map<CustomConfigPath, CustomSetting> | undefined;
private get customSettings() {
if (this._customSettings == null) {
this._customSettings = new Map<CustomConfigPath, CustomSetting>([
[
'rebaseEditor.enabled',
{
name: 'workbench.editorAssociations',
enabled: () => this.container.rebaseEditor.enabled,
update: this.container.rebaseEditor.setEnabled,
},
],
[
'currentLine.useUncommittedChangesFormat',
{
name: 'currentLine.uncommittedChangesFormat',
enabled: () => configuration.get('currentLine.uncommittedChangesFormat') != null,
update: async enabled =>
configuration.updateEffective(
'currentLine.uncommittedChangesFormat',
// eslint-disable-next-line no-template-curly-in-string
enabled ? '✏️ ${ago}' : null,
),
},
],
]);
}
return this._customSettings;
}
protected getCustomSettings(): Record<string, boolean> {
const customSettings: Record<string, boolean> = Object.create(null);
for (const [key, setting] of this.customSettings) {
customSettings[key] = setting.enabled();
}
return customSettings;
}
private notifyDidChangeConfiguration() {
// Make sure to get the raw config, not from the container which has the modes mixed in
return this.host.notify(DidChangeConfigurationNotificationType, {
config: configuration.getAll(true),
customSettings: this.getCustomSettings(),
});
}
}
interface CustomSetting {
name: ConfigPath | CoreConfiguration;
enabled: () => boolean;
update: (enabled: boolean) => Promise<void>;
}

+ 0
- 233
src/webviews/webviewWithConfigBase.ts 查看文件

@ -1,233 +0,0 @@
import type { ConfigurationChangeEvent, Disposable } from 'vscode';
import { ConfigurationTarget } from 'vscode';
import type { CoreConfiguration } from '../constants';
import { extensionPrefix } from '../constants';
import type { Container } from '../container';
import { CommitFormatter } from '../git/formatters/commitFormatter';
import { GitCommit, GitCommitIdentity } from '../git/models/commit';
import { GitFileChange, GitFileIndexStatus } from '../git/models/file';
import { PullRequest, PullRequestState } from '../git/models/pullRequest';
import type { ConfigPath } from '../system/configuration';
import { configuration } from '../system/configuration';
import { map } from '../system/iterable';
import { Logger } from '../system/logger';
import type { CustomConfigPath, IpcMessage } from './protocol';
import {
DidChangeConfigurationNotificationType,
DidGenerateConfigurationPreviewNotificationType,
GenerateConfigurationPreviewCommandType,
isCustomConfigKey,
onIpc,
UpdateConfigurationCommandType,
} from './protocol';
import type { WebviewController, WebviewProvider } from './webviewController';
export abstract class WebviewProviderWithConfigBase<State> implements WebviewProvider<State> {
private readonly _disposable: Disposable;
constructor(protected readonly container: Container, protected readonly host: WebviewController<State>) {
this._disposable = configuration.onDidChangeAny(this.onAnyConfigurationChanged, this);
}
dispose() {
this._disposable.dispose();
}
onActiveChanged(active: boolean): void {
// Anytime the webview becomes active, make sure it has the most up-to-date config
if (active) {
void this.notifyDidChangeConfiguration();
}
}
onMessageReceived(e: IpcMessage): void {
if (e == null) return;
switch (e.method) {
case UpdateConfigurationCommandType.method:
Logger.debug(`Webview(${this.host.id}).onMessageReceived: method=${e.method}`);
onIpc(UpdateConfigurationCommandType, e, async params => {
const target =
params.scope === 'workspace' ? ConfigurationTarget.Workspace : ConfigurationTarget.Global;
let key: keyof typeof params.changes;
for (key in params.changes) {
let value = params.changes[key];
if (isCustomConfigKey(key)) {
const customSetting = this.customSettings.get(key);
if (customSetting != null) {
if (typeof value === 'boolean') {
await customSetting.update(value);
} else {
debugger;
}
}
continue;
}
const inspect = configuration.inspect(key)!;
if (value != null) {
if (params.scope === 'workspace') {
if (value === inspect.workspaceValue) continue;
} else {
if (value === inspect.globalValue && value !== inspect.defaultValue) continue;
if (value === inspect.defaultValue) {
value = undefined;
}
}
}
await configuration.update(key as any, value, target);
}
for (const key of params.removes) {
await configuration.update(key as any, undefined, target);
}
});
break;
case GenerateConfigurationPreviewCommandType.method:
Logger.debug(`Webview(${this.host.id}).onMessageReceived: method=${e.method}`);
onIpc(GenerateConfigurationPreviewCommandType, e, async params => {
switch (params.type) {
case 'commit':
case 'commit-uncommitted': {
const commit = new GitCommit(
this.container,
'~/code/eamodio/vscode-gitlens-demo',
'fe26af408293cba5b4bfd77306e1ac9ff7ccaef8',
new GitCommitIdentity('You', 'eamodio@gmail.com', new Date('2016-11-12T20:41:00.000Z')),
new GitCommitIdentity('You', 'eamodio@gmail.com', new Date('2020-11-01T06:57:21.000Z')),
params.type === 'commit-uncommitted' ? 'Uncommitted changes' : 'Supercharged',
['3ac1d3f51d7cf5f438cc69f25f6740536ad80fef'],
params.type === 'commit-uncommitted' ? 'Uncommitted changes' : 'Supercharged',
new GitFileChange(
'~/code/eamodio/vscode-gitlens-demo',
'code.ts',
GitFileIndexStatus.Modified,
),
undefined,
[],
);
let includePullRequest = false;
switch (params.key) {
case configuration.name('currentLine.format'):
includePullRequest = configuration.get('currentLine.pullRequests.enabled');
break;
case configuration.name('statusBar.format'):
includePullRequest = configuration.get('statusBar.pullRequests.enabled');
break;
}
let pr: PullRequest | undefined;
if (includePullRequest) {
pr = new PullRequest(
{ id: 'github', name: 'GitHub', domain: 'github.com', icon: 'github' },
{
name: 'Eric Amodio',
avatarUrl: 'https://avatars1.githubusercontent.com/u/641685?s=32&v=4',
url: 'https://github.com/eamodio',
},
'1',
'Supercharged',
'https://github.com/gitkraken/vscode-gitlens/pulls/1',
PullRequestState.Merged,
new Date('Sat, 12 Nov 2016 19:41:00 GMT'),
undefined,
new Date('Sat, 12 Nov 2016 20:41:00 GMT'),
);
}
let preview;
try {
preview = CommitFormatter.fromTemplate(params.format, commit, {
dateFormat: configuration.get('defaultDateFormat'),
pullRequestOrRemote: pr,
messageTruncateAtNewLine: true,
});
} catch {
preview = 'Invalid format';
}
await this.host.notify(
DidGenerateConfigurationPreviewNotificationType,
{ preview: preview },
e.completionId,
);
}
}
});
break;
}
}
private onAnyConfigurationChanged(e: ConfigurationChangeEvent) {
if (!configuration.changedAny(e, extensionPrefix)) {
const notify = configuration.changedAny<CustomSetting['name']>(e, [
...map(this.customSettings.values(), s => s.name),
]);
if (!notify) return;
}
void this.notifyDidChangeConfiguration();
}
private _customSettings: Map<CustomConfigPath, CustomSetting> | undefined;
private get customSettings() {
if (this._customSettings == null) {
this._customSettings = new Map<CustomConfigPath, CustomSetting>([
[
'rebaseEditor.enabled',
{
name: 'workbench.editorAssociations',
enabled: () => this.container.rebaseEditor.enabled,
update: this.container.rebaseEditor.setEnabled,
},
],
[
'currentLine.useUncommittedChangesFormat',
{
name: 'currentLine.uncommittedChangesFormat',
enabled: () => configuration.get('currentLine.uncommittedChangesFormat') != null,
update: async enabled =>
configuration.updateEffective(
'currentLine.uncommittedChangesFormat',
// eslint-disable-next-line no-template-curly-in-string
enabled ? '✏️ ${ago}' : null,
),
},
],
]);
}
return this._customSettings;
}
protected getCustomSettings(): Record<string, boolean> {
const customSettings: Record<string, boolean> = Object.create(null);
for (const [key, setting] of this.customSettings) {
customSettings[key] = setting.enabled();
}
return customSettings;
}
private notifyDidChangeConfiguration() {
// Make sure to get the raw config, not from the container which has the modes mixed in
return this.host.notify(DidChangeConfigurationNotificationType, {
config: configuration.getAll(true),
customSettings: this.getCustomSettings(),
});
}
}
interface CustomSetting {
name: ConfigPath | CoreConfiguration;
enabled: () => boolean;
update: (enabled: boolean) => Promise<void>;
}

正在加载...
取消
保存