diff --git a/packages/filesystem/src/browser/file-service.ts b/packages/filesystem/src/browser/file-service.ts index 61f03400f56e4..f237cd7560bc5 100644 --- a/packages/filesystem/src/browser/file-service.ts +++ b/packages/filesystem/src/browser/file-service.ts @@ -237,12 +237,12 @@ export interface FileSystemProviderCapabilitiesChangeEvent { } export interface FileSystemProviderReadOnlyMessageChangeEvent { - /** The affected file system provider for which this event was fired. */ - provider: FileSystemProvider; - /** The uri for which the provider is registered */ - scheme: string; - /** The new read only message */ - message: MarkdownString | undefined; + /** The affected file system provider for which this event was fired. */ + provider: FileSystemProvider; + /** The uri for which the provider is registered */ + scheme: string; + /** The new read only message */ + message: MarkdownString | undefined; } /** @@ -378,7 +378,7 @@ export class FileService { providerDisposables.push(provider.onFileWatchError(() => this.handleFileWatchError())); providerDisposables.push(provider.onDidChangeCapabilities(() => this.onDidChangeFileSystemProviderCapabilitiesEmitter.fire({ provider, scheme }))); if (ReadOnlyMessageFileSystemProvider.is(provider)) { - providerDisposables.push(provider.onDidChangeReadOnlyMessage(message => this.onDidChangeFileSystemProviderReadOnlyMessageEmitter.fire({ provider, scheme, message}))); + providerDisposables.push(provider.onDidChangeReadOnlyMessage(message => this.onDidChangeFileSystemProviderReadOnlyMessageEmitter.fire({ provider, scheme, message }))); } return Disposable.create(() => { diff --git a/packages/notebook/src/browser/notebook-editor-widget.tsx b/packages/notebook/src/browser/notebook-editor-widget.tsx index 533f7c6c850ef..19a1fce82c1d2 100644 --- a/packages/notebook/src/browser/notebook-editor-widget.tsx +++ b/packages/notebook/src/browser/notebook-editor-widget.tsx @@ -243,4 +243,14 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa this.viewportService.dispose(); super.dispose(); } + + protected override onAfterShow(msg: Message): void { + super.onAfterShow(msg); + this.notebookEditorService.notebookEditorFocusChanged(this, true); + } + + protected override onAfterHide(msg: Message): void { + super.onAfterHide(msg); + this.notebookEditorService.notebookEditorFocusChanged(this, false); + } } diff --git a/packages/notebook/src/browser/service/notebook-editor-widget-service.ts b/packages/notebook/src/browser/service/notebook-editor-widget-service.ts index 8038b6cdf90c2..5c64eb35e2c0f 100644 --- a/packages/notebook/src/browser/service/notebook-editor-widget-service.ts +++ b/packages/notebook/src/browser/service/notebook-editor-widget-service.ts @@ -50,18 +50,7 @@ export class NotebookEditorWidgetService { @postConstruct() protected init(): void { this.applicationShell.onDidChangeActiveWidget(event => { - if (event.newValue instanceof NotebookEditorWidget) { - if (event.newValue !== this.focusedEditor) { - this.focusedEditor = event.newValue; - this.contextKeyService.setContext(NOTEBOOK_EDITOR_FOCUSED, true); - this.onDidChangeFocusedEditorEmitter.fire(this.focusedEditor); - } - } else if (event.newValue) { - // Only unfocus editor if a new widget has been focused - this.focusedEditor = undefined; - this.contextKeyService.setContext(NOTEBOOK_EDITOR_FOCUSED, true); - this.onDidChangeFocusedEditorEmitter.fire(undefined); - } + this.notebookEditorFocusChanged(event.newValue as NotebookEditorWidget, event.newValue instanceof NotebookEditorWidget); }); } @@ -92,4 +81,18 @@ export class NotebookEditorWidgetService { return Array.from(this.notebookEditors.values()); } + notebookEditorFocusChanged(editor: NotebookEditorWidget, focus: boolean): void { + if (focus) { + if (editor !== this.focusedEditor) { + this.focusedEditor = editor; + this.contextKeyService.setContext(NOTEBOOK_EDITOR_FOCUSED, true); + this.onDidChangeFocusedEditorEmitter.fire(this.focusedEditor); + } + } else if (this.focusedEditor) { + this.focusedEditor = undefined; + this.contextKeyService.setContext(NOTEBOOK_EDITOR_FOCUSED, false); + this.onDidChangeFocusedEditorEmitter.fire(undefined); + } + } + } diff --git a/packages/plugin-ext/src/main/browser/notebooks/notebook-kernels-main.ts b/packages/plugin-ext/src/main/browser/notebooks/notebook-kernels-main.ts index a35aaf3b30453..93dde74a3a473 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/notebook-kernels-main.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/notebook-kernels-main.ts @@ -27,7 +27,6 @@ import { CellExecution, NotebookEditorWidgetService, NotebookExecutionStateService, NotebookKernelChangeEvent, NotebookKernelService, NotebookService } from '@theia/notebook/lib/browser'; -import { combinedDisposable } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle'; import { interfaces } from '@theia/core/shared/inversify'; import { NotebookKernelSourceAction } from '@theia/notebook/lib/common'; import { NotebookDto } from './notebook-dto'; @@ -153,6 +152,20 @@ export class NotebookKernelsMainImpl implements NotebookKernelsMain { } }); }); + this.notebookKernelService.onDidChangeSelectedKernel(e => { + if (e.newKernel) { + const newKernelHandle = Array.from(this.kernels.entries()).find(([_, [kernel]]) => kernel.id === e.newKernel)?.[0]; + if (newKernelHandle !== undefined) { + this.proxy.$acceptNotebookAssociation(newKernelHandle, e.notebook.toComponents(), true); + } + } else { + const oldKernelHandle = Array.from(this.kernels.entries()).find(([_, [kernel]]) => kernel.id === e.oldKernel)?.[0]; + if (oldKernelHandle !== undefined) { + this.proxy.$acceptNotebookAssociation(oldKernelHandle, e.notebook.toComponents(), false); + } + + } + }); } async $postMessage(handle: number, editorId: string | undefined, message: unknown): Promise { @@ -196,16 +209,8 @@ export class NotebookKernelsMainImpl implements NotebookKernelsMain { } }(handle, data, this.languageService); - const listener = this.notebookKernelService.onDidChangeSelectedKernel(e => { - if (e.oldKernel === kernel.id) { - this.proxy.$acceptNotebookAssociation(handle, e.notebook.toComponents(), false); - } else if (e.newKernel === kernel.id) { - this.proxy.$acceptNotebookAssociation(handle, e.notebook.toComponents(), true); - } - }); - const registration = this.notebookKernelService.registerKernel(kernel); - this.kernels.set(handle, [kernel, combinedDisposable(listener, registration)]); + this.kernels.set(handle, [kernel, registration]); } diff --git a/packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx b/packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx index 934cc61308f38..0d4c48e4f8a87 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx +++ b/packages/plugin-ext/src/main/browser/notebooks/renderers/cell-output-webview.tsx @@ -99,6 +99,7 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable { if (this.editor) { this.toDispose.push(this.editor.onDidPostKernelMessage(message => { + // console.log('from extension customKernelMessage ', JSON.stringify(message)); this.webviewWidget.sendMessage({ type: 'customKernelMessage', message @@ -106,6 +107,7 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable { })); this.toDispose.push(this.editor.onPostRendererMessage(messageObj => { + // console.log('from extension customRendererMessage ', JSON.stringify(messageObj)); this.webviewWidget.sendMessage({ type: 'customRendererMessage', ...messageObj @@ -188,6 +190,7 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable { this.updateOutput({ newOutputs: this.cell.outputs, start: 0, deleteCount: 0 }); break; case 'customRendererMessage': + // console.log('from webview customRendererMessage ', message.rendererId, '', JSON.stringify(message.message)); this.messagingService.getScoped(this.editor.id).postMessage(message.rendererId, message.message); break; case 'didRenderOutput': @@ -197,6 +200,7 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable { this.editor.node.getElementsByClassName('theia-notebook-viewport')[0].children[0].scrollBy(message.deltaX, message.deltaY); break; case 'customKernelMessage': + // console.log('from webview customKernelMessage ', JSON.stringify(message.message)); this.editor.recieveKernelMessage(message.message); break; } diff --git a/packages/plugin-ext/src/main/browser/notebooks/renderers/output-webview-internal.ts b/packages/plugin-ext/src/main/browser/notebooks/renderers/output-webview-internal.ts index 5eb60f62ab5a0..6f38f53abb358 100644 --- a/packages/plugin-ext/src/main/browser/notebooks/renderers/output-webview-internal.ts +++ b/packages/plugin-ext/src/main/browser/notebooks/renderers/output-webview-internal.ts @@ -573,5 +573,13 @@ export async function outputWebviewPreload(ctx: PreloadContext): Promise { }); window.addEventListener('wheel', handleWheel); + (document.head as HTMLHeadElement & { originalAppendChild: typeof document.head.appendChild }).originalAppendChild = document.head.appendChild; + (document.head as HTMLHeadElement & { originalAppendChild: typeof document.head.appendChild }).appendChild = function appendChild(node: T): T { + if (node instanceof HTMLScriptElement && node.src.includes('webviewuuid')) { + node.src = node.src.replace('webviewuuid', location.hostname.split('.')[0]); + } + return this.originalAppendChild(node); + }; + theia.postMessage({ type: 'initialized' }); } diff --git a/packages/plugin-ext/src/plugin/notebook/notebook-kernels.ts b/packages/plugin-ext/src/plugin/notebook/notebook-kernels.ts index 363d9d8657d42..e09d8fff69e22 100644 --- a/packages/plugin-ext/src/plugin/notebook/notebook-kernels.ts +++ b/packages/plugin-ext/src/plugin/notebook/notebook-kernels.ts @@ -20,11 +20,11 @@ import { CellExecuteUpdateDto, NotebookKernelDto, NotebookKernelsExt, NotebookKernelsMain, - NotebookKernelSourceActionDto, NotebookOutputDto, PluginModel, PluginPackage, PLUGIN_RPC_CONTEXT + NotebookKernelSourceActionDto, NotebookOutputDto, PluginModel, PLUGIN_RPC_CONTEXT } from '../../common'; import { RPCProtocol } from '../../common/rpc-protocol'; import { UriComponents } from '../../common/uri-components'; -import { CancellationTokenSource, Disposable, DisposableCollection, Emitter, Path } from '@theia/core'; +import { CancellationTokenSource, Disposable, DisposableCollection, Emitter } from '@theia/core'; import { Cell } from './notebook-document'; import { NotebooksExtImpl } from './notebooks'; import { NotebookCellOutputConverter, NotebookCellOutputItem, NotebookKernelSourceAction } from '../type-converters'; @@ -34,6 +34,8 @@ import { CommandRegistryImpl } from '../command-registry'; import { NotebookCellOutput, NotebookRendererScript, URI } from '../types-impl'; import { toUriComponents } from '../../main/browser/hierarchy/hierarchy-types-converters'; import type * as theia from '@theia/plugin'; +import { WebviewsExtImpl } from '../webviews'; +import { WorkspaceExtImpl } from '../workspace'; interface KernelData { extensionId: string; @@ -64,8 +66,21 @@ export class NotebookKernelsExtImpl implements NotebookKernelsExt { rpc: RPCProtocol, private readonly notebooks: NotebooksExtImpl, private readonly commands: CommandRegistryImpl, + private readonly webviews: WebviewsExtImpl, + workspace: WorkspaceExtImpl ) { this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.NOTEBOOK_KERNELS_MAIN); + + // call onDidChangeSelection for all kernels after trust is granted to inform extensions they can set the kernel as assoiciated + // the jupyter extension for example does not set kernel association after trust is granted + workspace.onDidGrantWorkspaceTrust(() => { + this.kernelData.forEach(kernel => { + kernel.associatedNotebooks.forEach(async (_, uri) => { + const notebook = await this.notebooks.waitForNotebookDocument(URI.parse(uri)); + kernel.onDidChangeSelection.fire({ selected: true, notebook: notebook.apiNotebook }); + }); + }); + }); } private currentHandle = 0; @@ -216,8 +231,7 @@ export class NotebookKernelsExtImpl implements NotebookKernelsExt { return that.proxy.$postMessage(handle, 'notebook:' + editor?.notebook.uri.toString(), message); }, asWebviewUri(localResource: theia.Uri): theia.Uri { - const basePath = PluginPackage.toPluginUrl(extension, ''); - return URI.from({ path: new Path(basePath).join(localResource.path).toString(), scheme: 'https' }); + return that.webviews.toGeneralWebviewResource(extension, localResource); } }; @@ -294,20 +308,20 @@ export class NotebookKernelsExtImpl implements NotebookKernelsExt { }; } - async $acceptNotebookAssociation(handle: number, uri: UriComponents, value: boolean): Promise { + async $acceptNotebookAssociation(handle: number, uri: UriComponents, selected: boolean): Promise { const obj = this.kernelData.get(handle); if (obj) { // update data structure const notebook = await this.notebooks.waitForNotebookDocument(URI.from(uri)); - if (value) { + if (selected) { obj.associatedNotebooks.set(notebook.uri.toString(), true); } else { obj.associatedNotebooks.delete(notebook.uri.toString()); } - console.debug(`NotebookController[${handle}] ASSOCIATE notebook`, notebook.uri.toString(), value); + console.debug(`NotebookController[${handle}] ASSOCIATE notebook`, notebook.uri.toString(), selected); // send event obj.onDidChangeSelection.fire({ - selected: value, + selected: selected, notebook: notebook.apiNotebook }); } diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 1114cc2e7244c..83c3f80a5c312 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -276,7 +276,7 @@ export function createAPIFactory( const notebooksExt = rpc.set(MAIN_RPC_CONTEXT.NOTEBOOKS_EXT, new NotebooksExtImpl(rpc, commandRegistry, editorsAndDocumentsExt, documents)); const notebookEditors = rpc.set(MAIN_RPC_CONTEXT.NOTEBOOK_EDITORS_EXT, new NotebookEditorsExtImpl(notebooksExt)); const notebookRenderers = rpc.set(MAIN_RPC_CONTEXT.NOTEBOOK_RENDERERS_EXT, new NotebookRenderersExtImpl(rpc, notebooksExt)); - const notebookKernels = rpc.set(MAIN_RPC_CONTEXT.NOTEBOOK_KERNELS_EXT, new NotebookKernelsExtImpl(rpc, notebooksExt, commandRegistry)); + const notebookKernels = rpc.set(MAIN_RPC_CONTEXT.NOTEBOOK_KERNELS_EXT, new NotebookKernelsExtImpl(rpc, notebooksExt, commandRegistry, webviewExt, workspaceExt)); const notebookDocuments = rpc.set(MAIN_RPC_CONTEXT.NOTEBOOK_DOCUMENTS_EXT, new NotebookDocumentsExtImpl(notebooksExt)); const statusBarMessageRegistryExt = new StatusBarMessageRegistryExt(rpc); const terminalExt = rpc.set(MAIN_RPC_CONTEXT.TERMINAL_EXT, new TerminalServiceExtImpl(rpc)); @@ -744,7 +744,7 @@ export function createAPIFactory( registerTextDocumentContentProvider(scheme: string, provider: theia.TextDocumentContentProvider): theia.Disposable { return workspaceExt.registerTextDocumentContentProvider(scheme, provider); }, - registerFileSystemProvider(scheme: string, provider: theia.FileSystemProvider, options?: { isCaseSensitive?: boolean, isReadonly?: boolean | MarkdownString}): + registerFileSystemProvider(scheme: string, provider: theia.FileSystemProvider, options?: { isCaseSensitive?: boolean, isReadonly?: boolean | MarkdownString }): theia.Disposable { return fileSystemExt.registerFileSystemProvider(scheme, provider, options); }, diff --git a/packages/plugin-ext/src/plugin/webviews.ts b/packages/plugin-ext/src/plugin/webviews.ts index d80fe9d873e2d..e9aff70fb9bb8 100644 --- a/packages/plugin-ext/src/plugin/webviews.ts +++ b/packages/plugin-ext/src/plugin/webviews.ts @@ -24,6 +24,7 @@ import { fromViewColumn, toViewColumn, toWebviewPanelShowOptions } from './type- import { Disposable, WebviewPanelTargetArea, URI } from './types-impl'; import { WorkspaceExtImpl } from './workspace'; import { PluginIconPath } from './plugin-icon-path'; +import { PluginModel, PluginPackage } from '../common'; @injectable() export class WebviewsExtImpl implements WebviewsExt { @@ -196,6 +197,14 @@ export class WebviewsExtImpl implements WebviewsExt { return undefined; } + toGeneralWebviewResource(extension: PluginModel, resource: theia.Uri): theia.Uri { + const extensionUri = URI.parse(extension.packageUri); + const relativeResourcePath = resource.path.replace(extensionUri.path, ''); + const basePath = PluginPackage.toPluginUrl(extension, '') + relativeResourcePath; + + return URI.parse(this.initData!.webviewResourceRoot.replace('{{uuid}}', 'webviewUUID')).with({ path: basePath }); + } + public deleteWebview(handle: string): void { this.webviews.delete(handle); }