浏览代码

Adds ability to switch the Commit Graph layout

- Can switch between Editor (default) and Panel layouts
 - Panel layout adds an additional Commit Graph Details view (similar to Commit Details) on the right side of the Commit Graph
main
Eric Amodio 1年前
父节点
当前提交
36baba1d77
共有 22 个文件被更改,包括 267 次插入120 次删除
  1. +73
    -21
      package.json
  2. +1
    -1
      src/config.ts
  3. +1
    -1
      src/constants.ts
  4. +13
    -4
      src/container.ts
  5. +1
    -1
      src/plus/webviews/focus/registration.ts
  6. +19
    -13
      src/plus/webviews/graph/graphWebview.ts
  7. +18
    -6
      src/plus/webviews/graph/registration.ts
  8. +2
    -2
      src/plus/webviews/timeline/registration.ts
  9. +3
    -3
      src/plus/webviews/timeline/timelineWebview.ts
  10. +2
    -1
      src/storage.ts
  11. +5
    -1
      src/webviews/apps/plus/graph/graph.html
  12. +1
    -1
      src/webviews/apps/plus/graph/graph.scss
  13. +26
    -10
      src/webviews/commitDetails/commitDetailsWebview.ts
  14. +3
    -2
      src/webviews/commitDetails/protocol.ts
  15. +24
    -2
      src/webviews/commitDetails/registration.ts
  16. +1
    -1
      src/webviews/home/registration.ts
  17. +1
    -1
      src/webviews/settings/registration.ts
  18. +5
    -4
      src/webviews/settings/settingsWebview.ts
  19. +55
    -33
      src/webviews/webviewController.ts
  20. +10
    -10
      src/webviews/webviewsController.ts
  21. +1
    -1
      src/webviews/welcome/registration.ts
  22. +2
    -1
      src/webviews/welcome/welcomeWebview.ts

+ 73
- 21
package.json 查看文件

@ -64,6 +64,7 @@
"onView:gitlens.views.searchAndCompare",
"onView:gitlens.views.worktrees",
"onView:gitlens.views.commitDetails",
"onView:gitlens.views.graphDetails",
"onWebviewPanel:gitlens.welcome",
"onWebviewPanel:gitlens.settings",
"onWebviewPanel:gitlens.graph",
@ -2384,16 +2385,16 @@
"scope": "window",
"order": 50
},
"gitlens.graph.experimental.location": {
"gitlens.graph.layout": {
"type": "string",
"default": "tab",
"default": "editor",
"enum": [
"tab",
"view"
"editor",
"panel"
],
"enumDescriptions": [
"Shows the Commit Graph in an editor tab",
"Shows the Commit Graph in a view (side bar, panel, etc)"
"Shows the Commit Graph in the bottom panel"
],
"markdownDescription": "Specifies the location in which the _Commit Graph_ will be shown",
"scope": "window",
@ -6874,6 +6875,16 @@
"icon": "$(refresh)"
},
{
"command": "gitlens.graph.switchToEditorLayout",
"title": "Switch Commit Graph to Editor Layout",
"category": "GitLens+"
},
{
"command": "gitlens.graph.switchToPanelLayout",
"title": "Switch Commit Graph to Panel Layout",
"category": "GitLens+"
},
{
"command": "gitlens.graph.push",
"title": "Push",
"category": "GitLens",
@ -7535,11 +7546,11 @@
},
{
"command": "gitlens.showGraphPage",
"when": "gitlens:enabled && config.gitlens.graph.experimental.location == tab"
"when": "gitlens:enabled && config.gitlens.graph.layout == editor"
},
{
"command": "gitlens.showGraphView",
"when": "gitlens:enabled && config.gitlens.graph.experimental.location == view"
"when": "gitlens:enabled && config.gitlens.graph.layout == panel"
},
{
"command": "gitlens.showHomeView",
@ -9102,6 +9113,14 @@
"when": "false"
},
{
"command": "gitlens.graph.switchToEditorLayout",
"when": "gitlens:enabled && config.gitlens.graph.layout != editor"
},
{
"command": "gitlens.graph.switchToPanelLayout",
"when": "gitlens:enabled && config.gitlens.graph.layout != panel"
},
{
"command": "gitlens.graph.push",
"when": "false"
},
@ -9588,7 +9607,7 @@
"group": "navigation@-99"
},
{
"command": "gitlens.showSettingsPage#commit-graph",
"submenu": "gitlens/graph/configuration",
"when": "gitlens:webview:graph:active",
"group": "navigation@-98"
},
@ -10468,12 +10487,12 @@
},
{
"command": "gitlens.graph.refresh",
"when": "view =~ /^gitlens\\.views\\.graph/",
"when": "view =~ /^gitlens\\.views\\.graph\\b/",
"group": "navigation@-99"
},
{
"command": "gitlens.showSettingsPage#commit-graph",
"when": "view =~ /^gitlens\\.views\\.graph/",
"submenu": "gitlens/graph/configuration",
"when": "view =~ /^gitlens\\.views\\.graph\\b/",
"group": "navigation@-98"
},
{
@ -12488,6 +12507,22 @@
"group": "2_gitlens@2"
}
],
"gitlens/graph/configuration": [
{
"command": "gitlens.graph.switchToEditorLayout",
"group": "1_gitlens@1",
"when": "config.gitlens.graph.layout != editor"
},
{
"command": "gitlens.graph.switchToPanelLayout",
"group": "1_gitlens@1",
"when": "config.gitlens.graph.layout != panel"
},
{
"command": "gitlens.showSettingsPage#commit-graph",
"group": "9_gitlens@1"
}
],
"gitlens/scm/resourceGroup/changes": [
{
"command": "gitlens.externalDiffAll",
@ -12707,6 +12742,11 @@
"label": "Commit Changes"
},
{
"id": "gitlens/graph/configuration",
"label": "Commit Graph Settings",
"icon": "$(gear)"
},
{
"id": "gitlens/scm/resourceGroup/changes",
"label": "Open Changes"
},
@ -13131,15 +13171,6 @@
"initialSize": 5
},
{
"type": "webview",
"id": "gitlens.views.graph",
"name": "Commit Graph",
"when": "!gitlens:disabled && gitlens:plus:enabled && config.gitlens.graph.experimental.location == view",
"contextualTitle": "GitLens",
"icon": "$(gitlens-graph)",
"visibility": "collapsed"
},
{
"id": "gitlens.views.contributors",
"name": "Contributors",
"when": "!gitlens:disabled",
@ -13152,12 +13183,33 @@
"gitlensPanel": [
{
"type": "webview",
"id": "gitlens.views.graph",
"name": "Graph",
"when": "!gitlens:disabled && gitlens:plus:enabled && config.gitlens.graph.layout == panel",
"contextualTitle": "GitLens",
"icon": "$(gitlens-graph)",
"visibility": "visible",
"initialSize": 12
},
{
"type": "webview",
"id": "gitlens.views.graphDetails",
"name": "Graph Details",
"when": "!gitlens:disabled && gitlens:plus:enabled && config.gitlens.graph.layout == panel",
"contextualTitle": "GitLens",
"icon": "$(gitlens-commit-view)",
"visibility": "visible",
"initialSize": 4
},
{
"type": "webview",
"id": "gitlens.views.timeline",
"name": "Visual File History",
"when": "!gitlens:disabled && gitlens:plus:enabled",
"contextualTitle": "GitLens",
"icon": "$(gitlens-history-view)",
"visibility": "visible"
"visibility": "collapsed",
"initialSize": 0
}
],
"scm": [

+ 1
- 1
src/config.ts 查看文件

@ -417,13 +417,13 @@ export interface GraphConfig {
defaultItemLimit: number;
dimMergeCommits: boolean;
experimental: {
location: 'tab' | 'view';
minimap: {
enabled: boolean;
additionalTypes: GraphMinimapTypes[];
};
};
highlightRowsOnRefHover: boolean;
layout: 'editor' | 'panel';
scrollRowPadding: number;
showDetailsView: 'open' | 'selection' | false;
showGhostRefsOnRowHover: boolean;

+ 1
- 1
src/constants.ts 查看文件

@ -302,7 +302,7 @@ export const enum Commands {
export type CustomEditorIds = 'rebase';
export type WebviewIds = 'graph' | 'settings' | 'timeline' | 'welcome' | 'focus';
export type WebviewViewIds = 'commitDetails' | 'graph' | 'home' | 'timeline';
export type WebviewViewIds = 'commitDetails' | 'graph' | 'graphDetails' | 'home' | 'timeline';
export type ContextKeys =
| `${typeof extensionPrefix}:action:${string}`

+ 13
- 4
src/container.ts 查看文件

@ -59,7 +59,10 @@ import { ViewCommands } from './views/viewCommands';
import { ViewFileDecorationProvider } from './views/viewDecorationProvider';
import { WorktreesView } from './views/worktreesView';
import { VslsController } from './vsls/vsls';
import { registerCommitDetailsWebviewView } from './webviews/commitDetails/registration';
import {
registerCommitDetailsWebviewView,
registerGraphDetailsWebviewView,
} from './webviews/commitDetails/registration';
import { registerHomeWebviewView } from './webviews/home/registration';
import { RebaseEditorProvider } from './webviews/rebase/rebaseEditor';
import { registerSettingsWebviewCommands, registerSettingsWebviewPanel } from './webviews/settings/registration';
@ -215,7 +218,7 @@ export class Container {
context.subscriptions.unshift((this._graphPanel = registerGraphWebviewPanel(this._webviews)));
context.subscriptions.unshift(registerGraphWebviewCommands(this, this._graphPanel));
if (configuration.get('graph.experimental.location') === 'view') {
if (configuration.get('graph.layout') === 'panel') {
context.subscriptions.unshift((this._graphView = registerGraphWebviewView(this._webviews)));
}
context.subscriptions.unshift(new GraphStatusBarController(this));
@ -231,6 +234,7 @@ export class Container {
context.subscriptions.unshift((this._repositoriesView = new RepositoriesView(this)));
context.subscriptions.unshift((this._commitDetailsView = registerCommitDetailsWebviewView(this._webviews)));
context.subscriptions.unshift((this._graphDetailsView = registerGraphDetailsWebviewView(this._webviews)));
context.subscriptions.unshift((this._commitsView = new CommitsView(this)));
context.subscriptions.unshift((this._fileHistoryView = new FileHistoryView(this)));
context.subscriptions.unshift((this._lineHistoryView = new LineHistoryView(this)));
@ -306,8 +310,8 @@ export class Container {
this.ensureModeApplied();
}
if (configuration.changed(e, 'graph.experimental.location')) {
if (configuration.get('graph.experimental.location') === 'view') {
if (configuration.changed(e, 'graph.layout')) {
if (configuration.get('graph.layout') === 'panel') {
this._graphPanel?.close();
this._graphView = registerGraphWebviewView(this._webviews);
} else {
@ -447,6 +451,11 @@ export class Container {
}
}
private readonly _graphDetailsView: WebviewViewProxy;
get graphDetailsView() {
return this._graphDetailsView;
}
private readonly _graphPanel: WebviewPanelProxy;
private _graphView: WebviewViewProxy | undefined;
get graphView() {

+ 1
- 1
src/plus/webviews/focus/registration.ts 查看文件

@ -15,7 +15,7 @@ export function registerFocusWebviewPanel(controller: WebviewsController) {
trackingFeature: 'focusWebview',
plusFeature: true,
column: ViewColumn.Active,
webviewPanelOptions: {
webviewHostOptions: {
retainContextWhenHidden: true,
enableFindWidget: true,
},

+ 19
- 13
src/plus/webviews/graph/graphWebview.ts 查看文件

@ -1,5 +1,5 @@
import type { ColorTheme, ConfigurationChangeEvent, Uri } from 'vscode';
import { CancellationTokenSource, Disposable, env, ViewColumn, window } from 'vscode';
import type { ColorTheme, ConfigurationChangeEvent, Uri, ViewColumn } from 'vscode';
import { CancellationTokenSource, Disposable, env, window } from 'vscode';
import type { CreatePullRequestActionContext } from '../../../api/gitlens';
import { getAvatarUri } from '../../../avatars';
import type {
@ -47,7 +47,12 @@ import {
import { getRemoteIconUri } from '../../../git/models/remote';
import { RemoteResourceType } from '../../../git/models/remoteResource';
import type { RepositoryChangeEvent, RepositoryFileSystemChangeEvent } from '../../../git/models/repository';
import { Repository, RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository';
import {
isRepository,
Repository,
RepositoryChange,
RepositoryChangeComparisonMode,
} from '../../../git/models/repository';
import type { GitSearch } from '../../../git/search';
import { getSearchQueryComparisonKey } from '../../../git/search';
import { showRepositoryPicker } from '../../../quickpicks/repositoryPicker';
@ -237,18 +242,15 @@ export class GraphWebviewProvider implements WebviewProvider {
this._disposable.dispose();
}
async canShowWebviewPanel(
firstTime: boolean,
options: { column?: ViewColumn; preserveFocus?: boolean },
async onShowing(
loading: boolean,
_options: { column?: ViewColumn; preserveFocus?: boolean },
...args: unknown[]
): Promise<boolean> {
this._firstSelection = true;
if (options.column == null) {
options.column = ViewColumn.Active;
}
const context = args[0];
if (context instanceof Repository) {
if (isRepository(context)) {
this.repository = context;
} else if (hasGitReference(context)) {
this.repository = this.container.git.getRepository(context.ref.repoPath);
@ -280,7 +282,7 @@ export class GraphWebviewProvider implements WebviewProvider {
this.repository = context.node.repo;
}
if (this.repository != null && !firstTime && this.host.isReady) {
if (this.repository != null && !loading && this.host.isReady) {
this.updateState();
}
}
@ -696,8 +698,12 @@ export class GraphWebviewProvider implements WebviewProvider {
},
);
if (!this.container.commitDetailsView.loaded) {
void this.container.commitDetailsView.show({ preserveFocus: e.preserveFocus }, {
const details =
configuration.get('graph.layout') === 'panel'
? this.container.graphDetailsView
: this.container.commitDetailsView;
if (!details.ready) {
void details.show({ preserveFocus: e.preserveFocus }, {
commit: commit,
interaction: 'active',
preserveVisibility: false,

+ 18
- 6
src/plus/webviews/graph/registration.ts 查看文件

@ -24,7 +24,7 @@ export function registerGraphWebviewPanel(controller: WebviewsController) {
trackingFeature: 'graphWebview',
plusFeature: true,
column: ViewColumn.Active,
webviewPanelOptions: {
webviewHostOptions: {
retainContextWhenHidden: true,
enableFindWidget: false,
},
@ -33,7 +33,7 @@ export function registerGraphWebviewPanel(controller: WebviewsController) {
const { GraphWebviewProvider } = await import(/* webpackChunkName: "graph" */ './graphWebview');
return new GraphWebviewProvider(container, host);
},
() => configuration.get('graph.experimental.location') === 'tab',
() => configuration.get('graph.layout') === 'editor',
);
}
@ -46,7 +46,7 @@ export function registerGraphWebviewView(controller: WebviewsController) {
contextKeyPrefix: `gitlens:webviewView:graph`,
trackingFeature: 'graphView',
plusFeature: true,
webviewViewOptions: {
webviewHostOptions: {
retainContextWhenHidden: true,
},
},
@ -54,17 +54,29 @@ export function registerGraphWebviewView(controller: WebviewsController) {
const { GraphWebviewProvider } = await import(/* webpackChunkName: "graph" */ './graphWebview');
return new GraphWebviewProvider(container, host);
},
() => configuration.get('graph.experimental.location') === 'view',
() => configuration.get('graph.layout') === 'panel',
);
}
export function registerGraphWebviewCommands(container: Container, webview: WebviewPanelProxy) {
return Disposable.from(
registerCommand(Commands.ShowGraph, (...args: any[]) =>
configuration.get('graph.experimental.location') === 'view'
configuration.get('graph.layout') === 'panel'
? executeCommand(Commands.ShowGraphView, ...args)
: executeCommand(Commands.ShowGraphPage, ...args),
),
registerCommand('gitlens.graph.switchToEditorLayout', async () => {
await configuration.updateEffective('graph.layout', 'editor');
queueMicrotask(() => void executeCommand(Commands.ShowGraphPage));
}),
registerCommand('gitlens.graph.switchToPanelLayout', async () => {
await configuration.updateEffective('graph.layout', 'panel');
queueMicrotask(async () => {
await executeCommand('gitlens.views.graph.resetViewLocation');
await executeCommand('gitlens.views.graphDetails.resetViewLocation');
void executeCommand(Commands.ShowGraphView);
});
}),
registerCommand(
Commands.ShowInCommitGraph,
(
@ -78,7 +90,7 @@ export function registerGraphWebviewCommands(container: Container, webview: Webv
| TagNode,
) => {
const preserveFocus = 'preserveFocus' in args ? args.preserveFocus ?? false : false;
if (configuration.get('graph.experimental.location') === 'view') {
if (configuration.get('graph.layout') === 'panel') {
void container.graphView.show({ preserveFocus: preserveFocus }, args);
} else {
void webview.show({ preserveFocus: preserveFocus }, args);

+ 2
- 2
src/plus/webviews/timeline/registration.ts 查看文件

@ -15,7 +15,7 @@ export function registerTimelineWebviewPanel(controller: WebviewsController) {
trackingFeature: 'timelineWebview',
plusFeature: true,
column: ViewColumn.Active,
webviewPanelOptions: {
webviewHostOptions: {
retainContextWhenHidden: true,
enableFindWidget: false,
},
@ -36,7 +36,7 @@ export function registerTimelineWebviewView(controller: WebviewsController) {
contextKeyPrefix: `gitlens:webviewView:timeline`,
trackingFeature: 'timelineView',
plusFeature: true,
webviewViewOptions: {
webviewHostOptions: {
retainContextWhenHidden: false,
},
},

+ 3
- 3
src/plus/webviews/timeline/timelineWebview.ts 查看文件

@ -75,8 +75,8 @@ export class TimelineWebviewProvider implements WebviewProvider {
this._disposable.dispose();
}
canShowWebviewPanel(
firstTime: boolean,
onShowing(
loading: boolean,
_options: { column?: ViewColumn; preserveFocus?: boolean },
...args: unknown[]
): boolean {
@ -87,7 +87,7 @@ export class TimelineWebviewProvider implements WebviewProvider {
this.updatePendingEditor(window.activeTextEditor);
}
if (firstTime) {
if (loading) {
this._context = { ...this._context, ...this._pendingContext };
this._pendingContext = undefined;
} else {

+ 2
- 1
src/storage.ts 查看文件

@ -7,6 +7,7 @@ import type { StoredSearchQuery } from './git/search';
import type { Subscription } from './subscription';
import { debug } from './system/decorators/log';
import type { TrackedUsage, TrackedUsageKeys } from './telemetry/usageTracker';
import type { CommitDetailsDismissed } from './webviews/commitDetails/protocol';
import type { CompletedActions } from './webviews/home/protocol';
export type StorageChangeEvent =
@ -148,7 +149,7 @@ export type GlobalStorage = {
preVersion: string;
'views:layout': StoredViewsLayout;
'views:welcome:visible': boolean;
'views:commitDetails:dismissed': string[];
'views:commitDetails:dismissed': CommitDetailsDismissed[];
} & { [key in `provider:authentication:skip:${string}`]: boolean };
export type DeprecatedWorkspaceStorage = {

+ 5
- 1
src/webviews/apps/plus/graph/graph.html 查看文件

@ -7,7 +7,11 @@
</script>
</head>
<body class="graph-app scrollable" data-vscode-context='{ "preventDefaultContextMenuItems": true }'>
<body
class="graph-app scrollable"
data-placement="#{placement}"
data-vscode-context='{ "preventDefaultContextMenuItems": true }'
>
<div id="root" class="graph-app__container"></div>
#{endOfBody}
<style nonce="#{cspNonce}">

+ 1
- 1
src/webviews/apps/plus/graph/graph.scss 查看文件

@ -675,7 +675,7 @@ button:not([disabled]),
flex: none;
z-index: 2000;
position: relative;
margin-right: 2px;
margin: -1px -1px 0 -1px;
}
&__footer {

+ 26
- 10
src/webviews/commitDetails/commitDetailsWebview.ts 查看文件

@ -100,14 +100,22 @@ export class CommitDetailsWebviewProvider implements WebviewProvider
constructor(
private readonly container: Container,
private readonly host: WebviewController<State, Serialized<State>>,
private readonly options: { mode: 'default' | 'graph' },
) {
let dismissed = this.container.storage.get('views:commitDetails:dismissed');
if (options.mode === 'graph') {
if (dismissed == null) {
dismissed = ['sidebar'];
}
}
this._context = {
pinned: false,
commit: undefined,
preferences: {
autolinksExpanded: this.container.storage.getWorkspace('views:commitDetails:autolinksExpanded'),
avatars: configuration.get('views.commitDetails.avatars'),
dismissed: this.container.storage.get('views:commitDetails:dismissed'),
dismissed: dismissed,
files: configuration.get('views.commitDetails.files'),
},
richStateLoaded: false,
@ -133,8 +141,8 @@ export class CommitDetailsWebviewProvider implements WebviewProvider
this._disposable.dispose();
}
async canShowWebviewView(
_firstTime: boolean,
async onShowing(
_loading: boolean,
options: { column?: ViewColumn; preserveFocus?: boolean },
...args: unknown[]
): Promise<boolean> {
@ -166,8 +174,14 @@ export class CommitDetailsWebviewProvider implements WebviewProvider
}
private onCommitSelected(e: CommitSelectedEvent) {
if (e.data == null) return;
if (this._pinned && e.data.interaction === 'passive') return;
if (
e.data == null ||
(this._pinned && e.data.interaction === 'passive') ||
(this.options.mode === 'graph' && e.source !== 'gitlens.views.graph') ||
(this.options.mode === 'default' && e.source === 'gitlens.views.graph')
) {
return;
}
void this.host.show(false, { preserveFocus: e.data.preserveFocus }, e.data);
}
@ -270,11 +284,13 @@ export class CommitDetailsWebviewProvider implements WebviewProvider
this,
);
const { lineTracker } = this.container;
this._lineTrackerDisposable = lineTracker.subscribe(
this,
lineTracker.onDidChangeActiveLines(this.onActiveEditorLinesChanged, this),
);
if (this.options.mode !== 'graph') {
const { lineTracker } = this.container;
this._lineTrackerDisposable = lineTracker.subscribe(
this,
lineTracker.onDidChangeActiveLines(this.onActiveEditorLinesChanged, this),
);
}
}
private get isLineTrackerSuspended() {

+ 3
- 2
src/webviews/commitDetails/protocol.ts 查看文件

@ -11,6 +11,7 @@ import { IpcCommandType, IpcNotificationType } from '../protocol';
export const messageHeadlineSplitterToken = '\x00\n\x00';
export type FileShowOptions = TextDocumentShowOptions;
export type CommitDetailsDismissed = 'sidebar';
export type CommitSummary = {
sha: string;
@ -31,7 +32,7 @@ export type CommitDetails = CommitSummary & {
export type Preferences = {
autolinksExpanded?: boolean;
avatars?: boolean;
dismissed?: string[];
dismissed?: CommitDetailsDismissed[];
files?: Config['views']['commitDetails']['files'];
};
@ -93,7 +94,7 @@ export const NavigateCommitCommandType = new IpcCommandType('com
export interface PreferenceParams {
autolinksExpanded?: boolean;
avatars?: boolean;
dismissed?: string[];
dismissed?: CommitDetailsDismissed[];
files?: Config['views']['commitDetails']['files'];
}
export const PreferencesCommandType = new IpcCommandType<PreferenceParams>('commit/preferences');

+ 24
- 2
src/webviews/commitDetails/registration.ts 查看文件

@ -11,7 +11,7 @@ export function registerCommitDetailsWebviewView(controller: WebviewsController)
contextKeyPrefix: `gitlens:webviewView:commitDetails`,
trackingFeature: 'commitDetailsView',
plusFeature: false,
webviewViewOptions: {
webviewHostOptions: {
retainContextWhenHidden: false,
},
},
@ -19,7 +19,29 @@ export function registerCommitDetailsWebviewView(controller: WebviewsController)
const { CommitDetailsWebviewProvider } = await import(
/* webpackChunkName: "commitDetails" */ './commitDetailsWebview'
);
return new CommitDetailsWebviewProvider(container, host);
return new CommitDetailsWebviewProvider(container, host, { mode: 'default' });
},
);
}
export function registerGraphDetailsWebviewView(controller: WebviewsController) {
return controller.registerWebviewView<State, Serialized<State>>(
{
id: 'gitlens.views.graphDetails',
fileName: 'commitDetails.html',
title: 'Commit Graph Details',
contextKeyPrefix: `gitlens:webviewView:commitDetails`,
trackingFeature: 'commitDetailsView',
plusFeature: false,
webviewHostOptions: {
retainContextWhenHidden: false,
},
},
async (container, host) => {
const { CommitDetailsWebviewProvider } = await import(
/* webpackChunkName: "commitDetails" */ './commitDetailsWebview'
);
return new CommitDetailsWebviewProvider(container, host, { mode: 'graph' });
},
);
}

+ 1
- 1
src/webviews/home/registration.ts 查看文件

@ -10,7 +10,7 @@ export function registerHomeWebviewView(controller: WebviewsController) {
contextKeyPrefix: `gitlens:webviewView:home`,
trackingFeature: 'homeView',
plusFeature: false,
webviewViewOptions: {
webviewHostOptions: {
retainContextWhenHidden: false,
},
},

+ 1
- 1
src/webviews/settings/registration.ts 查看文件

@ -16,7 +16,7 @@ export function registerSettingsWebviewPanel(controller: WebviewsController) {
trackingFeature: 'settingsWebview',
plusFeature: false,
column: ViewColumn.Beside,
webviewPanelOptions: {
webviewHostOptions: {
retainContextWhenHidden: false,
enableFindWidget: true,
},

+ 5
- 4
src/webviews/settings/settingsWebview.ts 查看文件

@ -2,20 +2,21 @@ import type { ViewColumn } from 'vscode';
import { workspace } from 'vscode';
import { configuration } from '../../system/configuration';
import { DidOpenAnchorNotificationType } from '../protocol';
import type { WebviewProvider } from '../webviewController';
import { WebviewProviderWithConfigBase } from '../webviewWithConfigBase';
import type { State } from './protocol';
export class SettingsWebviewProvider extends WebviewProviderWithConfigBase<State> {
export class SettingsWebviewProvider extends WebviewProviderWithConfigBase<State> implements WebviewProvider<State> {
private _pendingJumpToAnchor: string | undefined;
canShowWebviewPanel?(
firstTime: boolean,
onShowing?(
loading: boolean,
_options: { column?: ViewColumn; preserveFocus?: boolean },
...args: unknown[]
): boolean | Promise<boolean> {
const anchor = args[0];
if (anchor && typeof anchor === 'string') {
if (!firstTime && this.host.isReady && this.host.visible) {
if (!loading && this.host.isReady && this.host.visible) {
queueMicrotask(
() =>
void this.host.notify(DidOpenAnchorNotificationType, {

+ 55
- 33
src/webviews/webviewController.ts 查看文件

@ -14,6 +14,7 @@ import { setContext } from '../context';
import { executeCommand } from '../system/command';
import { debug, logName } from '../system/decorators/log';
import { serialize } from '../system/decorators/serialize';
import { isPromise } from '../system/promise';
import type { IpcMessage, IpcMessageParams, IpcNotificationType, WebviewFocusChangedParams } from './protocol';
import { ExecuteCommandType, onIpc, WebviewFocusChangedCommandType, WebviewReadyCommandType } from './protocol';
import type { WebviewPanelDescriptor, WebviewViewDescriptor } from './webviewsController';
@ -39,18 +40,8 @@ type GetParentType = T
: never;
export interface WebviewProvider<State, SerializedState = State> extends Disposable {
canShowWebviewPanel?(
firstTime: boolean,
options: { column?: ViewColumn; preserveFocus?: boolean },
...args: unknown[]
): boolean | Promise<boolean>;
onShowWebviewPanel?(
firstTime: boolean,
options: { column?: ViewColumn; preserveFocus?: boolean },
...args: unknown[]
): void | Promise<void>;
canShowWebviewView?(
firstTime: boolean,
onShowing?(
loading: boolean,
options: { column?: ViewColumn; preserveFocus?: boolean },
...args: unknown[]
): boolean | Promise<boolean>;
@ -133,6 +124,7 @@ export class WebviewController<
public readonly id: Descriptor['id'];
private _isReady: boolean = false;
private _suspended: boolean = false;
get isReady() {
return this._isReady;
}
@ -180,6 +172,7 @@ export class WebviewController<
this.provider.onVisibilityChanged?.(false);
this._isReady = false;
this._suspended = false;
this._onDidDispose.fire();
this.disposables.forEach(d => void d.dispose());
@ -231,36 +224,34 @@ export class WebviewController<
return this.parent.visible;
}
async show(firstTime: boolean, options?: { column?: ViewColumn; preserveFocus?: boolean }, ...args: unknown[]) {
async show(loading: boolean, options?: { column?: ViewColumn; preserveFocus?: boolean }, ...args: unknown[]) {
if (options == null) {
options = {};
}
if (this.isEditor()) {
const result = await this.provider.canShowWebviewPanel?.(firstTime, options, ...args);
if (result === false) return;
if (firstTime) {
this.webview.html = await this.getHtml(this.webview);
const result = this.provider.onShowing?.(loading, options, ...args);
if (result != null) {
if (isPromise(result)) {
if ((await result) === false) return;
} else if (result === false) {
return;
}
}
if (loading) {
this.webview.html = await this.getHtml(this.webview);
}
await this.provider.onShowWebviewPanel?.(firstTime, options, ...args);
if (!firstTime) {
if (this.isEditor()) {
if (!loading) {
this.parent.reveal(
options?.column ?? this.parent.viewColumn ?? this.descriptor.column ?? ViewColumn.Beside,
options?.preserveFocus ?? false,
options.column ?? this.parent.viewColumn ?? this.descriptor.column ?? ViewColumn.Beside,
options.preserveFocus ?? false,
);
}
} else if (this.isView()) {
const result = await this.provider.canShowWebviewView?.(firstTime, options, ...args);
if (result === false) return;
if (firstTime) {
this.webview.html = await this.getHtml(this.webview);
}
await executeCommand(`${this.id}.focus`, options);
if (firstTime) {
if (loading) {
this.provider.onVisibilityChanged?.(true);
}
}
@ -281,6 +272,8 @@ export class WebviewController<
// Mark the webview as not ready, until we know if we are changing the html
this._isReady = false;
this._suspended = false;
const html = await this.getHtml(this.webview);
if (force) {
// Reset the html to get the webview to reload
@ -310,6 +303,7 @@ export class WebviewController<
case WebviewReadyCommandType.method:
onIpc(WebviewReadyCommandType, e, () => {
this._isReady = true;
this._suspended = false;
this.provider.onReady?.();
});
@ -351,6 +345,20 @@ export class WebviewController<
})
private onParentViewStateChanged(e: WebviewPanelOnDidChangeViewStateEvent): void {
const { active, visible } = e.webviewPanel;
if (this.descriptor.webviewHostOptions?.retainContextWhenHidden !== true) {
if (visible) {
if (this._suspended) {
this._isReady = true;
this._suspended = false;
this.provider.onReady?.();
}
} else {
this._isReady = false;
this._suspended = true;
}
}
if (visible) {
setContextKeys(this.descriptor.contextKeyPrefix, active);
this.provider.onActiveChanged?.(active);
@ -369,6 +377,19 @@ export class WebviewController<
@debug()
private onParentVisibilityChanged(visible: boolean) {
if (this.descriptor.webviewHostOptions?.retainContextWhenHidden !== true) {
if (visible) {
if (this._suspended) {
this._isReady = true;
this._suspended = false;
this.provider.onReady?.();
}
} else {
this._isReady = false;
this._suspended = true;
}
}
if (visible) {
void this.container.usage.track(`${this.descriptor.trackingFeature}:shown`);
} else {
@ -454,14 +475,15 @@ export class WebviewController<
0: m => `{"id":${m.id},"method":${m.method}${m.completionId ? `,"completionId":${m.completionId}` : ''}}`,
},
})
postMessage(message: IpcMessage): Promise<boolean> {
async postMessage(message: IpcMessage): Promise<boolean> {
if (!this._isReady) return Promise.resolve(false);
// It looks like there is a bug where `postMessage` can sometimes just hang infinitely. Not sure why, but ensure we don't hang
return Promise.race<boolean>([
const success = await Promise.race<boolean>([
this.webview.postMessage(message),
new Promise<boolean>(resolve => setTimeout(resolve, 5000, false)),
]);
return success;
}
}

+ 10
- 10
src/webviews/webviewsController.ts 查看文件

@ -24,7 +24,7 @@ export interface WebviewPanelDescriptor {
readonly plusFeature: boolean;
readonly column?: ViewColumn;
readonly webviewOptions?: WebviewOptions;
readonly webviewPanelOptions?: WebviewPanelOptions;
readonly webviewHostOptions?: WebviewPanelOptions;
}
interface WebviewPanelRegistration<State, SerializedState = State> {
@ -34,7 +34,7 @@ interface WebviewPanelRegistration {
export interface WebviewPanelProxy extends Disposable {
readonly id: `gitlens.${WebviewIds}`;
readonly loaded: boolean;
readonly ready: boolean;
readonly visible: boolean;
close(): void;
refresh(force?: boolean): Promise<void>;
@ -49,7 +49,7 @@ export interface WebviewViewDescriptor {
readonly trackingFeature: TrackedUsageFeatures;
readonly plusFeature: boolean;
readonly webviewOptions?: WebviewOptions;
readonly webviewViewOptions?: {
readonly webviewHostOptions?: {
readonly retainContextWhenHidden?: boolean;
};
}
@ -62,7 +62,7 @@ interface WebviewViewRegistration {
export interface WebviewViewProxy extends Disposable {
readonly id: `gitlens.views.${WebviewViewIds}`;
readonly loaded: boolean;
readonly ready: boolean;
readonly visible: boolean;
refresh(force?: boolean): Promise<void>;
show(options?: { preserveFocus?: boolean }, ...args: unknown[]): Promise<void>;
@ -142,7 +142,7 @@ export class WebviewsController implements Disposable {
}
},
},
descriptor.webviewViewOptions != null ? { webviewOptions: descriptor.webviewViewOptions } : undefined,
descriptor.webviewHostOptions != null ? { webviewOptions: descriptor.webviewHostOptions } : undefined,
),
);
@ -150,8 +150,8 @@ export class WebviewsController implements Disposable {
this.disposables.push(disposable);
return {
id: descriptor.id,
get loaded() {
return registration.controller != null;
get ready() {
return registration.controller?.isReady ?? false;
},
get visible() {
return registration.controller?.visible ?? false;
@ -219,7 +219,7 @@ export class WebviewsController implements Disposable {
localResourceRoots: [Uri.file(container.context.extensionPath)],
},
...descriptor.webviewOptions,
...descriptor.webviewPanelOptions,
...descriptor.webviewHostOptions,
},
);
panel.iconPath = Uri.file(container.context.asAbsolutePath(descriptor.iconPath));
@ -245,8 +245,8 @@ export class WebviewsController implements Disposable {
this.disposables.push(disposable);
return {
id: descriptor.id,
get loaded() {
return registration.controller != null;
get ready() {
return registration.controller?.isReady ?? false;
},
get visible() {
return registration.controller?.visible ?? false;

+ 1
- 1
src/webviews/welcome/registration.ts 查看文件

@ -15,7 +15,7 @@ export function registerWelcomeWebviewPanel(controller: WebviewsController) {
trackingFeature: 'welcomeWebview',
plusFeature: false,
column: ViewColumn.Beside,
webviewPanelOptions: {
webviewHostOptions: {
retainContextWhenHidden: false,
enableFindWidget: true,
},

+ 2
- 1
src/webviews/welcome/welcomeWebview.ts 查看文件

@ -1,8 +1,9 @@
import { configuration } from '../../system/configuration';
import type { WebviewProvider } from '../webviewController';
import { WebviewProviderWithConfigBase } from '../webviewWithConfigBase';
import type { State } from './protocol';
export class WelcomeWebviewProvider extends WebviewProviderWithConfigBase<State> {
export class WelcomeWebviewProvider extends WebviewProviderWithConfigBase<State> implements WebviewProvider<State> {
includeBootstrap(): State {
return {
// Make sure to get the raw config, not from the container which has the modes mixed in

正在加载...
取消
保存