Ver código fonte

Improves multi-instance webview support

- Adds split commands to force a new webview (when allowed)
main
Eric Amodio 1 ano atrás
pai
commit
e18a9312fe
8 arquivos alterados com 168 adições e 55 exclusões
  1. +64
    -4
      package.json
  2. +6
    -4
      src/plus/webviews/focus/registration.ts
  3. +19
    -2
      src/plus/webviews/graph/graphWebview.ts
  4. +9
    -8
      src/plus/webviews/graph/registration.ts
  5. +6
    -4
      src/plus/webviews/timeline/registration.ts
  6. +26
    -4
      src/plus/webviews/timeline/timelineWebview.ts
  7. +8
    -0
      src/webviews/webviewController.ts
  8. +30
    -29
      src/webviews/webviewsController.ts

+ 64
- 4
package.json Ver arquivo

@ -4962,6 +4962,12 @@
"icon": "$(layers)"
},
{
"command": "gitlens.focus.split",
"title": "Split Focus View",
"category": "GitLens",
"icon": "$(split-horizontal)"
},
{
"command": "gitlens.showGraph",
"title": "Show Commit Graph",
"category": "GitLens",
@ -4974,6 +4980,12 @@
"icon": "$(gitlens-graph)"
},
{
"command": "gitlens.graph.split",
"title": "Split Commit Graph",
"category": "GitLens",
"icon": "$(split-horizontal)"
},
{
"command": "gitlens.showGraphView",
"title": "Show Commit Graph View",
"category": "GitLens",
@ -5130,6 +5142,12 @@
"icon": "$(graph-scatter)"
},
{
"command": "gitlens.timeline.split",
"title": "Split Visual File History",
"category": "GitLens",
"icon": "$(split-horizontal)"
},
{
"command": "gitlens.showStashesView",
"title": "Show Stashes View",
"category": "GitLens"
@ -8332,6 +8350,10 @@
"when": "gitlens:enabled"
},
{
"command": "gitlens.focus.split",
"when": "gitlens:enabled && config.gitlens.focus.experimental.allowMultipleInstances"
},
{
"command": "gitlens.showGraph",
"when": "gitlens:enabled"
},
@ -8340,6 +8362,10 @@
"when": "gitlens:enabled"
},
{
"command": "gitlens.graph.split",
"when": "gitlens:enabled && config.gitlens.graph.experimental.allowMultipleInstances"
},
{
"command": "gitlens.showGraphView",
"when": "gitlens:enabled"
},
@ -8448,6 +8474,10 @@
"when": "gitlens:enabled && gitlens:activeFileStatus =~ /tracked/"
},
{
"command": "gitlens.timeline.split",
"when": "gitlens:enabled && config.gitlens.visualHistory.experimental.allowMultipleInstances"
},
{
"command": "gitlens.showTimelineView",
"when": "gitlens:enabled"
},
@ -10609,23 +10639,38 @@
},
{
"command": "gitlens.timeline.refresh",
"when": "gitlens:webview:timeline:active",
"when": "activeWebviewPanelId === gitlens.timeline",
"group": "navigation@-99"
},
{
"command": "gitlens.graph.refresh",
"when": "gitlens:webview:graph:active",
"when": "activeWebviewPanelId === gitlens.graph",
"group": "navigation@-99"
},
{
"submenu": "gitlens/graph/configuration",
"when": "gitlens:webview:graph:active",
"when": "activeWebviewPanelId === gitlens.graph",
"group": "navigation@-98"
},
{
"command": "gitlens.focus.refresh",
"when": "gitlens:webview:focus:active",
"when": "activeWebviewPanelId === gitlens.focus",
"group": "navigation@-98"
},
{
"command": "gitlens.focus.split",
"when": "resourceScheme == webview-panel && activeWebviewPanelId === gitlens.focus && config.gitlens.focus.experimental.allowMultipleInstances",
"group": "navigation@-97"
},
{
"command": "gitlens.graph.split",
"when": "resourceScheme == webview-panel && activeWebviewPanelId === gitlens.graph && config.gitlens.graph.experimental.allowMultipleInstances",
"group": "navigation@-97"
},
{
"command": "gitlens.timeline.split",
"when": "resourceScheme == webview-panel && activeWebviewPanelId === gitlens.timeline && config.gitlens.visualHistory.experimental.allowMultipleInstances",
"group": "navigation@-97"
}
],
"editor/title/context": [
@ -10658,6 +10703,21 @@
"submenu": "gitlens/editor/history",
"when": "gitlens:enabled && config.gitlens.menus.editorTab.history && isFileSystemResource && resourceScheme =~ /^(?!output$|vscode-(?!remote|vfs$)).*$/",
"group": "2_a_gitlens_open_file@1"
},
{
"command": "gitlens.focus.split",
"when": "resourceScheme == webview-panel && activeWebviewPanelId === gitlens.focus && config.gitlens.focus.experimental.allowMultipleInstances",
"group": "6_split_in_group_gitlens@2"
},
{
"command": "gitlens.graph.split",
"when": "resourceScheme == webview-panel && activeWebviewPanelId === gitlens.graph && config.gitlens.graph.experimental.allowMultipleInstances",
"group": "6_split_in_group_gitlens@2"
},
{
"command": "gitlens.timeline.split",
"when": "resourceScheme == webview-panel && activeWebviewPanelId === gitlens.timeline && config.gitlens.visualHistory.experimental.allowMultipleInstances",
"group": "6_split_in_group_gitlens@2"
}
],
"explorer/context": [

+ 6
- 4
src/plus/webviews/focus/registration.ts Ver arquivo

@ -7,7 +7,7 @@ import type { State } from './protocol';
export function registerFocusWebviewPanel(controller: WebviewsController) {
return controller.registerWebviewPanel<State>(
{ id: Commands.ShowFocusPage, options: { preserveInstance: false } },
{ id: Commands.ShowFocusPage, options: { preserveInstance: true } },
{
id: 'gitlens.focus',
fileName: 'focus.html',
@ -32,8 +32,10 @@ export function registerFocusWebviewPanel(controller: WebviewsController) {
export function registerFocusWebviewCommands(panels: WebviewPanelsProxy) {
return Disposable.from(
registerCommand(`${panels.id}.refresh`, () => {
void panels.getActiveOrFirstInstance()?.refresh(true);
}),
registerCommand(`${panels.id}.refresh`, () => void panels.getActiveInstance()?.refresh(true)),
registerCommand(
`${panels.id}.split`,
() => void panels.show({ preserveInstance: false, column: ViewColumn.Beside }),
),
);
}

+ 19
- 2
src/plus/webviews/graph/graphWebview.ts Ver arquivo

@ -84,7 +84,7 @@ import { RepositoryFolderNode } from '../../../views/nodes/viewNode';
import type { IpcMessage, IpcNotificationType } from '../../../webviews/protocol';
import { onIpc } from '../../../webviews/protocol';
import type { WebviewController, WebviewProvider } from '../../../webviews/webviewController';
import type { WebviewPanelShowCommandArgs } from '../../../webviews/webviewsController';
import type { WebviewPanelShowCommandArgs, WebviewShowOptions } from '../../../webviews/webviewsController';
import { isSerializedState } from '../../../webviews/webviewsController';
import type { SubscriptionChangeEvent } from '../../subscription/subscriptionService';
import type {
@ -289,6 +289,23 @@ export class GraphWebviewProvider implements WebviewProvider {
this._disposable.dispose();
}
canReuseInstance(_options?: WebviewShowOptions, ...args: unknown[]): boolean | undefined {
if (this.container.git.openRepositoryCount === 1) return true;
const [arg] = args;
let repository: Repository | undefined;
if (isRepository(arg)) {
repository = arg;
} else if (hasGitReference(arg)) {
repository = this.container.git.getRepository(arg.ref.repoPath);
} else if (isSerializedState<State>(arg) && arg.state.selectedRepository != null) {
repository = this.container.git.getRepository(arg.state.selectedRepository);
}
return repository == null ? undefined : repository.uri.toString() === this.repository?.uri.toString();
}
async onShowing(
loading: boolean,
_options: { column?: ViewColumn; preserveFocus?: boolean },
@ -372,7 +389,7 @@ export class GraphWebviewProvider implements WebviewProvider {
() =>
void executeCommand<WebviewPanelShowCommandArgs>(
Commands.ShowGraphPage,
{ _type: 'WebviewPanelShowOptions', preserveInstance: true },
{ _type: 'WebviewPanelShowOptions' },
this.repository,
),
),

+ 9
- 8
src/plus/webviews/graph/registration.ts Ver arquivo

@ -19,7 +19,7 @@ import type { ShowInCommitGraphCommandArgs, State } from './protocol';
export function registerGraphWebviewPanel(controller: WebviewsController) {
return controller.registerWebviewPanel<State>(
{ id: Commands.ShowGraphPage, options: { preserveInstance: false } },
{ id: Commands.ShowGraphPage, options: { preserveInstance: true } },
{
id: 'gitlens.graph',
fileName: 'graph.html',
@ -69,7 +69,7 @@ export function registerGraphWebviewCommands(container: Container, panels: Webvi
? executeCommand(Commands.ShowGraphView, ...args)
: executeCommand<WebviewPanelShowCommandArgs>(
Commands.ShowGraphPage,
{ _type: 'WebviewPanelShowOptions', preserveInstance: true },
{ _type: 'WebviewPanelShowOptions' },
undefined,
...args,
),
@ -80,7 +80,6 @@ export function registerGraphWebviewCommands(container: Container, panels: Webvi
() =>
void executeCommand<WebviewPanelShowCommandArgs>(Commands.ShowGraphPage, {
_type: 'WebviewPanelShowOptions',
preserveInstance: true,
}),
);
}),
@ -123,8 +122,8 @@ export function registerGraphWebviewCommands(container: Container, panels: Webvi
if (configuration.get('graph.layout') === 'panel') {
void container.graphView.show({ preserveFocus: preserveFocus }, args);
} else {
const active = panels.getActiveInstance()?.instanceId;
void panels.show({ preserveFocus: preserveFocus, preserveInstance: active ?? true }, args);
// const active = panels.getActiveInstance()?.instanceId;
void panels.show({ preserveFocus: preserveFocus /*preserveInstance: active ?? true*/ }, args);
}
},
),
@ -144,8 +143,10 @@ export function registerGraphWebviewCommands(container: Container, panels: Webvi
void container.graphView.show({ preserveFocus: preserveFocus }, args);
},
),
registerCommand(`${panels.id}.refresh`, () => {
void panels.getActiveOrFirstInstance()?.refresh(true);
}),
registerCommand(`${panels.id}.refresh`, () => void panels.getActiveInstance()?.refresh(true)),
registerCommand(
`${panels.id}.split`,
() => void panels.show({ preserveInstance: false, column: ViewColumn.Beside }),
),
);
}

+ 6
- 4
src/plus/webviews/timeline/registration.ts Ver arquivo

@ -7,7 +7,7 @@ import type { State } from './protocol';
export function registerTimelineWebviewPanel(controller: WebviewsController) {
return controller.registerWebviewPanel<State>(
{ id: Commands.ShowTimelinePage, options: { preserveInstance: false } },
{ id: Commands.ShowTimelinePage, options: { preserveInstance: true } },
{
id: 'gitlens.timeline',
fileName: 'timeline.html',
@ -52,8 +52,10 @@ export function registerTimelineWebviewView(controller: WebviewsController) {
export function registerTimelineWebviewCommands(panels: WebviewPanelsProxy) {
return Disposable.from(
registerCommand(`${panels.id}.refresh`, () => {
void panels.getActiveOrFirstInstance()?.refresh(true);
}),
registerCommand(`${panels.id}.refresh`, () => void panels.getActiveInstance()?.refresh(true)),
registerCommand(
`${panels.id}.split`,
() => void panels.show({ preserveInstance: false, column: ViewColumn.Beside }),
),
);
}

+ 26
- 4
src/plus/webviews/timeline/timelineWebview.ts Ver arquivo

@ -1,4 +1,4 @@
import type { TextEditor, ViewColumn } from 'vscode';
import type { TextEditor } from 'vscode';
import { Disposable, Uri, window } from 'vscode';
import { Commands } from '../../../constants';
import type { Container } from '../../../container';
@ -23,7 +23,7 @@ import type { IpcMessage } from '../../../webviews/protocol';
import { onIpc } from '../../../webviews/protocol';
import type { WebviewController, WebviewProvider } from '../../../webviews/webviewController';
import { updatePendingContext } from '../../../webviews/webviewController';
import type { WebviewPanelShowCommandArgs } from '../../../webviews/webviewsController';
import type { WebviewPanelShowCommandArgs, WebviewShowOptions } from '../../../webviews/webviewsController';
import { isSerializedState } from '../../../webviews/webviewsController';
import type { SubscriptionChangeEvent } from '../../subscription/subscriptionService';
import type { Commit, Period, State } from './protocol';
@ -87,9 +87,31 @@ export class TimelineWebviewProvider implements WebviewProvider {
void this.notifyDidChangeState(true);
}
canReuseInstance(
_options?: WebviewShowOptions,
...args: [Uri | ViewFileNode | { state: Partial<State> }] | unknown[]
): boolean | undefined {
let uri: Uri | undefined;
const [arg] = args;
if (arg != null) {
if (arg instanceof Uri) {
uri = arg;
} else if (isViewFileNode(arg)) {
uri = arg.uri;
} else if (isSerializedState<State>(arg) && arg.state.uri != null) {
uri = Uri.parse(arg.state.uri);
}
} else {
uri = window.activeTextEditor?.document.uri;
}
return uri == null ? undefined : uri.toString() === this._context.uri?.toString();
}
onShowing(
loading: boolean,
_options: { column?: ViewColumn; preserveFocus?: boolean },
_options: WebviewShowOptions | undefined,
...args: [Uri | ViewFileNode | { state: Partial<State> }] | unknown[]
): boolean {
const [arg] = args;
@ -138,7 +160,7 @@ export class TimelineWebviewProvider implements WebviewProvider {
void executeCommand<WebviewPanelShowCommandArgs>(
Commands.ShowTimelinePage,
{ _type: 'WebviewPanelShowOptions', preserveInstance: true },
{ _type: 'WebviewPanelShowOptions' },
this._context.uri,
);
},

+ 8
- 0
src/webviews/webviewController.ts Ver arquivo

@ -41,6 +41,7 @@ type GetParentType = T
: never;
export interface WebviewProvider<State, SerializedState = State> extends Disposable {
canReuseInstance?(options?: WebviewShowOptions, ...args: unknown[]): boolean | undefined;
onShowing?(loading: boolean, options: WebviewShowOptions, ...args: unknown[]): boolean | Promise<boolean>;
registerCommands?(): Disposable[];
@ -261,6 +262,13 @@ export class WebviewController<
return this._disposed ? false : this.parent.visible;
}
canReuseInstance(options?: WebviewShowOptions, ...args: unknown[]): boolean | undefined {
if (!this.isEditor()) return undefined;
if (options?.column != null && options.column !== this.parent.viewColumn) return false;
return this.provider.canReuseInstance?.(options, ...args);
}
@debug({ args: false })
async show(loading: boolean, options?: WebviewShowOptions, ...args: unknown[]) {
if (options == null) {

+ 30
- 29
src/webviews/webviewsController.ts Ver arquivo

@ -58,7 +58,6 @@ export interface WebviewPanelsProxy extends Disposable {
readonly id: WebviewIds;
readonly instances: Iterable<WebviewPanelProxy>;
getActiveInstance(): WebviewPanelProxy | undefined;
getActiveOrFirstInstance(): WebviewPanelProxy | undefined;
show(options?: WebviewPanelsShowOptions, ...args: unknown[]): Promise<void>;
}
@ -269,15 +268,37 @@ export class WebviewsController implements Disposable {
column = ViewColumn.Active;
}
let preserveInstance: string | boolean;
// eslint-disable-next-line prefer-const
({ preserveInstance, ...options } = { preserveInstance: true, ...options });
let controller: WebviewController<State, SerializedState, WebviewPanelDescriptor> | undefined;
if (!descriptor.allowMultipleInstances || preserveInstance === true) {
controller = getActiveOrFirstController(registration.controllers);
} else if (preserveInstance != null && typeof preserveInstance === 'string') {
controller = registration.controllers?.get(preserveInstance);
if (registration.controllers?.size) {
if (descriptor.allowMultipleInstances) {
if (options?.preserveInstance !== false) {
if (options?.preserveInstance != null && typeof options.preserveInstance === 'string') {
controller = registration.controllers.get(options.preserveInstance);
}
if (controller == null) {
let active;
let first;
for (const c of registration.controllers.values()) {
first ??= c;
if (c.active) {
active = c;
}
if (c.canReuseInstance(options, ...args) === true) {
controller = c;
break;
}
}
if (controller == null && options?.preserveInstance === true) {
controller = active ?? first;
}
}
}
} else {
controller = first(registration.controllers)?.[1];
}
}
if (controller == null) {
@ -383,10 +404,6 @@ export class WebviewsController implements Disposable {
const controller = find(registration.controllers.values(), c => c.active ?? false);
return controller != null ? convertToWebviewPanelProxy(controller) : undefined;
},
getActiveOrFirstInstance: function () {
const controller = getActiveOrFirstController(registration.controllers);
return controller != null ? convertToWebviewPanelProxy(controller) : undefined;
},
dispose: function () {
disposable.dispose();
},
@ -416,22 +433,6 @@ interface WebviewViewShowOptions {
export type WebviewShowOptions = WebviewPanelShowOptions | WebviewViewShowOptions;
function getActiveOrFirstController<State, SerializedState>(
controllers: Map<string | undefined, WebviewController<State, SerializedState, WebviewPanelDescriptor>> | undefined,
) {
if (!controllers?.size) return undefined;
if (controllers.size === 1) return first(controllers.values());
let firstController;
for (const controller of controllers.values()) {
if (controller.active) return controller;
firstController ??= controller;
}
return firstController;
}
function convertToWebviewPanelProxy<State, SerializedState>(
controller: WebviewController<State, SerializedState, WebviewPanelDescriptor>,
): WebviewPanelProxy {

Carregando…
Cancelar
Salvar