Skip to content

Commit 6af9e16

Browse files
committed
fix(liveshare): resolve activation errors, file reading bugs, and add hooks for sess compatibility
1 parent 978e8f6 commit 6af9e16

8 files changed

Lines changed: 130 additions & 79 deletions

File tree

src/liveShare/index.ts

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import * as vsls from 'vsls';
99
import * as fs from 'fs-extra';
1010

1111
import { enableSessionWatcher, extensionContext } from '../extension';
12-
import { activateRSessionGuest, browserDisposables, initGuest } from './shareSession';
12+
import { docScheme, docProvider } from './virtualDocs';
13+
import * as path from 'path';
14+
import { browserDisposables } from './shareSession';
1315
import { initTreeView, rLiveShareProvider, shareWorkspace, ToggleNode } from './shareTree';
1416
import { Commands, Callback, liveShareOnRequest, liveShareRequest } from './shareCommands';
1517

@@ -79,6 +81,10 @@ export async function initLiveShare(context: vscode.ExtensionContext): Promise<v
7981
void vscode.commands.executeCommand('setContext', 'r.liveShare:isGuest', isGuestSession);
8082

8183
// push commands
84+
context.subscriptions.push(
85+
vscode.commands.registerCommand('r.activateRSessionGuest', () => activateRSessionGuest())
86+
);
87+
8288
if (!isGuestSession) {
8389
context.subscriptions.push(
8490
vscode.commands.registerCommand(
@@ -91,10 +97,6 @@ export async function initLiveShare(context: vscode.ExtensionContext): Promise<v
9197
}
9298
)
9399
);
94-
} else {
95-
context.subscriptions.push(
96-
vscode.commands.registerCommand('r.activateRSessionGuest', () => activateRSessionGuest())
97-
);
98100
}
99101
}
100102
}
@@ -144,6 +146,7 @@ export async function LiveSessionListener(): Promise<void> {
144146
break;
145147
case vsls.Role.Guest:
146148
console.log('[LiveSessionListener] guest event');
149+
initGuest(extensionContext);
147150
await rGuestService?.startService();
148151
break;
149152
case vsls.Role.Host:
@@ -165,6 +168,7 @@ export async function LiveSessionListener(): Promise<void> {
165168
break;
166169
case vsls.Role.Guest:
167170
console.log('[LiveSessionListener] guest event');
171+
initGuest(extensionContext);
168172
await rGuestService.startService();
169173
break;
170174
default:
@@ -200,7 +204,19 @@ export class HostService {
200204
// Provides core liveshare functionality
201205
// The shared service is used as a RPC service
202206
// to pass messages between the host and guests
203-
service = await liveSession.shareService(ShareProviderName);
207+
console.info(`[HostService] Attempting to share service: ${ShareProviderName}`);
208+
try {
209+
await liveSession.unshareService(ShareProviderName);
210+
} catch (e) {
211+
// ignore error if it wasn't shared
212+
}
213+
try {
214+
service = await liveSession.shareService(ShareProviderName);
215+
console.info(`[HostService] shareService returned: ${String(!!service)}`);
216+
} catch (e) {
217+
console.error(`[HostService] shareService threw error: ${String(e)}`);
218+
service = null;
219+
}
204220
if (service) {
205221
this._isStarted = true;
206222
for (const command in Commands.host) {
@@ -233,9 +249,9 @@ export class HostService {
233249
void this.notifyWorkspace(workspaceData);
234250
}
235251
}
236-
public notifyPlot(file: string): void {
252+
public notifyPlot(data: string, format: string): void {
237253
if (this._isStarted && shareWorkspace) {
238-
void liveShareRequest(Callback.NotifyPlotUpdate, file);
254+
void liveShareRequest(Callback.NotifyPlotUpdate, data, format);
239255
}
240256
}
241257
public notifyGuestPlotManager(url: string): void {
@@ -256,7 +272,9 @@ export class GuestService {
256272
return this._isStarted;
257273
}
258274
public async startService(): Promise<void> {
275+
console.info(`[GuestService] Attempting to get shared service: ${ShareProviderName}`);
259276
service = await liveSession.getSharedService(ShareProviderName);
277+
console.info(`[GuestService] getSharedService returned: ${String(!!service)}`);
260278
if (service) {
261279
this._isStarted = true;
262280
this.requestAttach();
@@ -352,3 +370,45 @@ async function sessionCleanup(): Promise<void> {
352370
rLiveShareProvider.refresh();
353371
}
354372
}
373+
374+
export let guestResDir: string;
375+
376+
export function initGuest(context: vscode.ExtensionContext): void {
377+
if (_sessionStatusBarItem) {
378+
console.info('initGuest: sessionStatusBarItem already exists');
379+
return;
380+
}
381+
console.info('Create guestSessionStatusBarItem');
382+
const sessionStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 1000);
383+
sessionStatusBarItem.command = 'r.activateRSessionGuest';
384+
sessionStatusBarItem.text = 'Guest R: (not attached)';
385+
sessionStatusBarItem.tooltip = 'Click to activate host R session';
386+
sessionStatusBarItem.show();
387+
context.subscriptions.push(
388+
sessionStatusBarItem,
389+
vscode.workspace.registerTextDocumentContentProvider(docScheme, docProvider)
390+
);
391+
_sessionStatusBarItem = sessionStatusBarItem;
392+
rGuestService?.setStatusBarItem(sessionStatusBarItem);
393+
guestResDir = path.join(context.extensionPath, 'dist', 'resources');
394+
}
395+
396+
export async function activateRSessionGuest(): Promise<void> {
397+
if (!isGuest()) {
398+
void vscode.window.showInformationMessage('This command is only available for guests.');
399+
return;
400+
}
401+
if (config().get<boolean>('sessionWatcher', true)) {
402+
console.info('[activateRSessionGuest]');
403+
if (!rGuestService?.isStarted()) {
404+
await rGuestService?.startService();
405+
}
406+
if (rGuestService?.isStarted()) {
407+
void rGuestService.requestAttach();
408+
} else {
409+
void vscode.window.showWarningMessage('Failed to connect to host R service. Please ensure the host has enabled sharing.');
410+
}
411+
} else {
412+
void vscode.window.showInformationMessage('This command requires that r.sessionWatcher be enabled.');
413+
}
414+
}

src/liveShare/shareCommands.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ export const Commands: ICommands = {
9595
[Callback.NotifyWorkspaceUpdate]: (args: [hostWorkspace: WorkspaceData]): void => {
9696
void updateGuestWorkspace(args[0]);
9797
},
98-
[Callback.NotifyPlotUpdate]: (args: [file: string]): void => {
99-
void updateGuestPlot(args[0]);
98+
[Callback.NotifyPlotUpdate]: (args: [data: string, format: string]): void => {
99+
void updateGuestPlot(args[0], args[1]);
100100
},
101101
[Callback.NotifyGuestPlotManager]: (args: [url: string]): void => {
102102
void globalPlotManager?.showHttpgdPlot(args[0]);
@@ -146,16 +146,8 @@ export function liveShareOnRequest(name: string, command: unknown, service: vsls
146146

147147
export function liveShareRequest(name: string, ...rest: unknown[]): unknown {
148148
if (isGuest()) {
149-
if (rest !== undefined) {
150-
return (service as vsls.SharedServiceProxy).request(name, rest);
151-
} else {
152-
return (service as vsls.SharedServiceProxy).request(name, []);
153-
}
149+
return (service as vsls.SharedServiceProxy).request(name, rest);
154150
} else {
155-
if (rest !== undefined) {
156-
return (service as vsls.SharedService).notify(name, { ...rest });
157-
} else {
158-
return (service as vsls.SharedService).notify(name, {});
159-
}
151+
return (service as vsls.SharedService).notify(name, rest);
160152
}
161153
}

src/liveShare/shareSession.ts

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import { extensionContext, globalPlotManager, globalRHelp, rWorkspace } from '..
55
import { asViewColumn, config, readContent } from '../util';
66
import { showBrowser, showDataView, WorkspaceData } from '../session';
77
import { showWebView } from '../webViewer';
8-
import { liveSession, UUID, rGuestService, _sessionStatusBarItem as sessionStatusBarItem } from '.';
8+
import { liveSession, UUID, rGuestService, _sessionStatusBarItem as sessionStatusBarItem, isGuest, guestResDir } from '.';
99
import { autoShareBrowser } from './shareTree';
1010
import { docProvider, docScheme } from './virtualDocs';
1111

1212
// Workspace Vars
1313
let guestPid: string;
1414
export let guestWorkspace: WorkspaceData | undefined;
15-
export let guestResDir: string;
1615
let rVer: string;
1716
let info: IRequest['info'];
1817

@@ -46,21 +45,7 @@ export interface IRequest {
4645
};
4746
}
4847

49-
export function initGuest(context: vscode.ExtensionContext): void {
50-
// create status bar item that contains info about the *guest* session watcher
51-
console.info('Create guestSessionStatusBarItem');
52-
const sessionStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 1000);
53-
sessionStatusBarItem.command = 'r.activateRSessionGuest';
54-
sessionStatusBarItem.text = 'Guest R: (not attached)';
55-
sessionStatusBarItem.tooltip = 'Click to activate host R session';
56-
sessionStatusBarItem.show();
57-
context.subscriptions.push(
58-
sessionStatusBarItem,
59-
vscode.workspace.registerTextDocumentContentProvider(docScheme, docProvider)
60-
);
61-
rGuestService?.setStatusBarItem(sessionStatusBarItem);
62-
guestResDir = path.join(context.extensionPath, 'dist', 'resources');
63-
}
48+
6449

6550
export function detachGuest(): void {
6651
console.info('[Guest Service] detach guest from workspace');
@@ -70,20 +55,13 @@ export function detachGuest(): void {
7055
rWorkspace?.refresh();
7156
}
7257

73-
export function activateRSessionGuest(): void {
74-
if (config().get<boolean>('sessionWatcher', true)) {
75-
console.info('[activateRSessionGuest]');
76-
void rGuestService?.requestAttach();
77-
} else {
78-
void vscode.window.showInformationMessage('This command requires that r.sessionWatcher be enabled.');
79-
}
80-
}
58+
8159

8260
// Guest version of session.ts updateRequest(), no need to check for changes in files
8361
// as this is handled by the session.ts variant
8462
// the force parameter is used for ensuring that the 'attach' case is appropriately called on guest join
8563
export async function updateGuestRequest(file: string, force: boolean = false): Promise<void> {
86-
const requestContent: string | undefined = await readContent(file, 'utf8');
64+
const requestContent: string | undefined = await rGuestService?.requestFileContent(file, 'utf8');
8765
if (!requestContent) {
8866
return;
8967
}
@@ -197,14 +175,11 @@ export function updateGuestWorkspace(hostWorkspace: WorkspaceData): void {
197175
// Instead of creating a file, we pass the base64 of the plot image
198176
// to the guest, and read that into an html page
199177
let panel: vscode.WebviewPanel | undefined = undefined;
200-
export async function updateGuestPlot(file: string): Promise<void> {
201-
const plotContent = await readContent(file, 'base64');
202-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
203-
178+
export function updateGuestPlot(data: string, format: string): void {
204179
const guestPlotView: vscode.ViewColumn = asViewColumn(config().get<string>('session.viewers.viewColumn.plot'), vscode.ViewColumn.Two);
205-
if (plotContent) {
180+
if (data) {
206181
if (panel) {
207-
panel.webview.html = getGuestImageHtml(plotContent);
182+
panel.webview.html = getGuestImageHtml(data, format);
208183
panel.reveal(guestPlotView, true);
209184
} else {
210185
panel = vscode.window.createWebviewPanel('dataview', 'R Guest Plot',
@@ -218,7 +193,7 @@ export async function updateGuestPlot(file: string): Promise<void> {
218193
retainContextWhenHidden: true,
219194
localResourceRoots: [vscode.Uri.file(guestResDir)],
220195
});
221-
const content = getGuestImageHtml(plotContent);
196+
const content = getGuestImageHtml(data, format);
222197
panel.webview.html = content;
223198
panel.onDidDispose(
224199
() => {
@@ -234,30 +209,41 @@ export async function updateGuestPlot(file: string): Promise<void> {
234209

235210
// Purely used in order to decode a base64 string into
236211
// an image format, bypassing saving a file onto the guest's system
237-
function getGuestImageHtml(content: string) {
212+
function getGuestImageHtml(content: string, format: string) {
213+
let imageSrc = '';
214+
if (format === 'svglite' || format === 'svg') {
215+
imageSrc = `data:image/svg+xml;base64,${String(content)}`;
216+
} else {
217+
imageSrc = `data:image/png;base64,${String(content)}`;
218+
}
219+
238220
return `
239221
<!doctype HTML>
240222
<html>
241223
<head>
242224
<meta charset="utf-8" />
243225
<meta name="viewport" content="width=device-width, initial-scale=1">
244226
<style type="text/css">
245-
body {
246-
color: black;
227+
body, html {
228+
margin: 0;
229+
padding: 0;
230+
width: 100%;
231+
height: 100%;
232+
overflow: hidden;
247233
background-color: var(--vscode-editor-background);
234+
display: flex;
235+
justify-content: center;
236+
align-items: center;
248237
}
249238
img {
250-
position: absolute;
251-
top:0;
252-
bottom: 0;
253-
left: 0;
254-
right: 0;
255-
margin: auto;
239+
max-width: 100%;
240+
max-height: 100%;
241+
object-fit: contain;
256242
}
257243
</style>
258244
</head>
259245
<body>
260-
<img src = "data:image/png;base64, ${String(content)}">
246+
<img src = "${imageSrc}">
261247
</body>
262248
</html>
263249
`;

src/plotViewer/httpgdViewer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class HttpgdManager {
4848
this.viewerOptions.fullWindow = conf.get('plot.defaults.fullWindowMode', false);
4949
this.viewerOptions.token = token;
5050
const viewer = new HttpgdViewer(host, this.viewerOptions);
51-
if (isHost() && autoShareBrowser) {
51+
if (isHost()) {
5252
const disposable = await shareServer(url, 'httpgd');
5353
viewer.webviewPanel?.onDidDispose(() => void disposable.dispose());
5454
}

src/plotViewer/standardViewer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as vscode from 'vscode';
33
import { asViewColumn, config, UriIcon } from '../util';
44
import { sessionRequest, server } from '../session';
55
import { PlotViewer } from './types';
6+
import { rHostService } from '../liveShare';
67

78
interface PlotResponse {
89
data: string;
@@ -99,6 +100,7 @@ export class StandardPlotViewer implements PlotViewer {
99100
data: this.plotData,
100101
format: this.plotFormat
101102
});
103+
rHostService?.notifyPlot(this.plotData, this.plotFormat);
102104
}
103105
}
104106

src/session.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,9 @@ async function handleNotification(message: Record<string, unknown>, ws: ExtWebSo
747747
case 'httpgd': {
748748
if (params.url) {
749749
await globalPlotManager?.showHttpgdPlot(String(params.url));
750+
if (isLiveShare() && isHost()) {
751+
rHostService?.notifyGuestPlotManager(String(params.url));
752+
}
750753
}
751754
break;
752755
}
@@ -787,6 +790,9 @@ async function handleNotification(message: Record<string, unknown>, ws: ExtWebSo
787790
if (viewer !== 'Disable') {
788791
await showDataView(String(params.source), String(params.type), String(params.title), String(params.file), viewer);
789792
}
793+
if (isLiveShare() && isHost()) {
794+
rHostService?.notifyRequest(String(params.file), false);
795+
}
790796
}
791797
break;
792798
}

0 commit comments

Comments
 (0)