瀏覽代碼

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

Loading…
取消
儲存