From d63f82d3ff86256f2a0357bfd5b4427fc8c7b96d Mon Sep 17 00:00:00 2001 From: Cristian Cepeda <43882+pastuxso@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:56:06 -0500 Subject: [PATCH] Refactor New Runme Notebooks Panel to use Runme Tasks --- src/extension/extension.ts | 12 +- src/extension/provider/codelens.ts | 14 +-- src/extension/provider/launcherBeta.ts | 166 ++++--------------------- src/extension/provider/runmeTask.ts | 57 ++++++--- 4 files changed, 79 insertions(+), 170 deletions(-) diff --git a/src/extension/extension.ts b/src/extension/extension.ts index 6482d5277..b7837ffa2 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -137,15 +137,9 @@ export class RunmeExtension { let treeViewer: RunmeTreeProvider if (kernel.isFeatureOn(FeatureName.NewTreeProvider)) { - await commands.executeCommand('setContext', 'runme.launcher.isExpanded', true) - await commands.executeCommand('setContext', 'runme.launcher.includeUnnamed', true) - treeViewer = new RunmeLauncherProviderBeta( - kernel, - server, - serializer, - getDefaultWorkspace(), - runner, - ) + await commands.executeCommand('setContext', 'runme.launcher.isExpanded', false) + await commands.executeCommand('setContext', 'runme.launcher.includeUnnamed', false) + treeViewer = new RunmeLauncherProviderBeta(kernel, serializer) } else { treeViewer = new RunmeLauncherProvider(getDefaultWorkspace()) } diff --git a/src/extension/provider/codelens.ts b/src/extension/provider/codelens.ts index 8c6f9ceda..219adf98e 100644 --- a/src/extension/provider/codelens.ts +++ b/src/extension/provider/codelens.ts @@ -214,15 +214,15 @@ export class RunmeCodeLensProvider implements CodeLensProvider, Disposable { break } - const task = await RunmeTaskProvider.newRunmeTask( - document.uri.fsPath, - getAnnotations(cell.metadata).name, + const task = await RunmeTaskProvider.newRunmeTask({ + filePath: document.uri.fsPath, + command: getAnnotations(cell.metadata).name, notebook, cell, - {}, - this.runner!, - this.kernel?.getRunnerEnvironment(), - ) + options: {}, + runner: this.runner!, + runnerEnv: this.kernel?.getRunnerEnvironment(), + }) await tasks.executeTask(task) } diff --git a/src/extension/provider/launcherBeta.ts b/src/extension/provider/launcherBeta.ts index 5573b8d9e..cdd7124c3 100644 --- a/src/extension/provider/launcherBeta.ts +++ b/src/extension/provider/launcherBeta.ts @@ -8,33 +8,15 @@ import { Uri, CancellationTokenSource, NotebookData, - NotebookCell, - NotebookCellData, commands, window, EventEmitter, + tasks as vscodeTasks, + Task, } from 'vscode' -import { GrpcTransport } from '@protobuf-ts/grpc-transport' -import { ServerStreamingCall } from '@protobuf-ts/runtime-rpc' -import { - firstValueFrom, - from, - isObservable, - lastValueFrom, - map, - Observable, - of, - toArray, -} from 'rxjs' - -import { initProjectClient, ProjectServiceClient, ReadyPromise } from '../grpc/client' -import KernelServer from '../server/kernelServer' -import { LoadEventFoundTask, LoadRequest, LoadResponse } from '../grpc/projectTypes' -import { Serializer } from '../../types' -import { RunmeIdentity } from '../grpc/serializerTypes' + import { asWorkspaceRelativePath, getAnnotations } from '../utils' import { Kernel } from '../kernel' -import type { IRunner } from '../runner' import getLogger from '../logger' import { SerializerBase } from '../serializer' import { LANGID_AND_EXTENSIONS } from '../../constants' @@ -44,14 +26,6 @@ import { OpenFileOptions, RunmeFile, RunmeTreeProvider } from './launcher' export const GLOB_PATTERN = '**/*.{md,mdr,mdx}' const logger = getLogger('LauncherBeta') -type LoadStream = ServerStreamingCall - -type ProjectTask = LoadEventFoundTask - -type TaskNotebook = NotebookData | Serializer.Notebook - -type TaskCell = NotebookCell | NotebookCellData | Serializer.Cell - /** * used to force VS Code update the tree view when user expands/collapses all * see https://github.com/microsoft/vscode/issues/172479 @@ -62,35 +36,15 @@ let sauceCount = 0 export class RunmeLauncherProvider implements RunmeTreeProvider { #disposables: Disposable[] = [] private allowUnnamed = false - private tasks: Promise - private ready: ReadyPromise - private client: ProjectServiceClient | undefined - private defaultItemState = TreeItemCollapsibleState.Expanded - private serverReadyListener: Disposable | undefined + private defaultItemState = TreeItemCollapsibleState.Collapsed private _onDidChangeTreeData = new EventEmitter() constructor( private kernel: Kernel, - private server: KernelServer, private serializer: SerializerBase, - private workspaceRoot?: string | undefined, - private runner?: IRunner, ) { const watcher = workspace.createFileSystemWatcher(GLOB_PATTERN, false, true, false) - this.serverReadyListener = this.server.onTransportReady(({ transport }) => - this.initProjectClient(transport), - ) - - this.ready = new Promise((resolve) => { - const disposable = server.onTransportReady(() => { - disposable.dispose() - resolve() - }) - }) - - this.tasks = lastValueFrom(this.loadProjectTasks()) - this.#disposables.push( watcher.onDidCreate((file) => logger.info('onDidCreate: ', file.fsPath)), watcher.onDidDelete((file) => logger.info('onDidDelete: ', file.fsPath)), @@ -152,7 +106,7 @@ export class RunmeLauncherProvider implements RunmeTreeProvider { } async getChildren(element?: RunmeFile | undefined): Promise { - const allTasks = await this.tasks + const tasks = await vscodeTasks.fetchTasks({ type: 'runme' }) if (!element) { /** @@ -160,19 +114,22 @@ export class RunmeLauncherProvider implements RunmeTreeProvider { * see https://github.com/microsoft/vscode/issues/172479 */ ++sauceCount - return Promise.resolve(await this.getNotebooks(allTasks)) + return Promise.resolve(this.getNotebooks(tasks)) } - return Promise.resolve(await this.getCells(allTasks, element)) + return Promise.resolve(await this.getCells(tasks, element)) } - async getNotebooks(tasks: LoadEventFoundTask[]): Promise { + getNotebooks(tasks: Task[]): RunmeFile[] { const foundTasks: RunmeFile[] = [] let prevDir: string | undefined for (const task of tasks) { - const { documentPath } = task + const { definition } = task + const fileUri = definition.fileUri as Uri + const documentPath = fileUri.path const { outside, relativePath } = asWorkspaceRelativePath(documentPath) + if (outside) { continue } @@ -195,16 +152,21 @@ export class RunmeLauncherProvider implements RunmeTreeProvider { return foundTasks } - async getCells(tasks: LoadEventFoundTask[], element: RunmeFile): Promise { + async getCells(tasks: Task[], element: RunmeFile): Promise { const foundTasks: RunmeFile[] = [] let mdBuffer: Uint8Array let prevFile: string | undefined - let notebook: Observable | undefined + let notebook: NotebookData | undefined for (const task of tasks) { - const { documentPath, name, id, isNameGenerated } = task + const { + name, + definition: { fileUri, isNameGenerated }, + } = task + const documentPath = fileUri?.path const { outside, relativePath } = asWorkspaceRelativePath(documentPath) + if (outside || (!this.allowUnnamed && isNameGenerated)) { continue } @@ -226,31 +188,20 @@ export class RunmeLauncherProvider implements RunmeTreeProvider { } const token = new CancellationTokenSource().token - notebook = from(this.serializer.deserializeNotebook(mdBuffer, token)) + notebook = await this.serializer.deserializeNotebook(mdBuffer, token) } if (!notebook) { continue } - const cell = notebook.pipe( - map((n) => { - return n.cells.find( - (cell) => cell.metadata?.['id'] === id || cell.metadata?.['runme.dev/name'] === name, - )! - }), - ) - - const notebookish = !isObservable(notebook) ? of(notebook) : notebook - const taskNotebook = await firstValueFrom(notebookish as any) - const cellish = !isObservable(cell) ? of(cell) : cell - const taskCell = await firstValueFrom(cellish as any) + const cell = notebook.cells.find((cell) => cell.metadata?.['runme.dev/name'] === name)! - const { excludeFromRunAll } = getAnnotations(taskCell.metadata) - const cellText = 'value' in taskCell ? taskCell.value : taskCell.document.getText() - const languageId = ('languageId' in taskCell && taskCell.languageId) || 'sh' - const cellIndex = taskNotebook.cells.findIndex( - (cell) => cell.metadata?.['id'] === id || cell.metadata?.['runme.dev/name'] === name, + const { excludeFromRunAll } = getAnnotations(cell.metadata) + const cellText = 'value' in cell ? cell.value : '' + const languageId = ('languageId' in cell && cell.languageId) || 'sh' + const cellIndex = notebook.cells.findIndex( + (cell) => cell.metadata?.['runme.dev/name'] === name, ) const lines = cellText.split('\n') @@ -285,69 +236,6 @@ export class RunmeLauncherProvider implements RunmeTreeProvider { this.#disposables.forEach((d) => d.dispose()) } - // Internal - - private async initProjectClient(transport?: GrpcTransport) { - this.client = initProjectClient(transport ?? (await this.server.transport())) - } - - protected loadProjectTasks(): Observable { - if (!workspace.workspaceFolders?.length) { - return of([]) - } - - const separator = workspace.workspaceFolders[0].uri.fsPath.indexOf('/') > -1 ? '/' : '\\' - - const requests = (workspace.workspaceFolders ?? []).map((folder) => { - return { - kind: { - oneofKind: 'directory', - directory: { - path: workspace.asRelativePath(folder.uri), - skipGitignore: false, - ignoreFilePatterns: [], - skipRepoLookupUpward: false, - }, - }, - identity: RunmeIdentity.ALL, - } - }) - - const task$ = new Observable((observer) => { - this.ready.then(() => - Promise.all( - requests.map((request) => { - const session: LoadStream = this.client!.load(request) - session.responses.onMessage((msg) => { - if (msg.data.oneofKind !== 'foundTask') { - return - } - observer.next(msg.data.foundTask) - }) - return session - }), - ).then(() => { - logger.info('Finished walk.') - observer.complete() - }), - ) - }) - - const dirProx = (pt: ProjectTask) => { - const { relativePath, outside } = asWorkspaceRelativePath(pt.documentPath) - const len = relativePath.split(separator).length - if (outside) { - return 100 * len - } - return len - } - - return task$.pipe( - toArray(), - map((tasks) => tasks.sort((a, b) => dirProx(a) - dirProx(b))), - ) - } - resolveExtension(languageId: string): string { const key = languageId.toLowerCase() return LANGID_AND_EXTENSIONS.get(key) || languageId diff --git a/src/extension/provider/runmeTask.ts b/src/extension/provider/runmeTask.ts index f18f77401..60b3f9a1a 100644 --- a/src/extension/provider/runmeTask.ts +++ b/src/extension/provider/runmeTask.ts @@ -64,6 +64,17 @@ type TaskNotebook = NotebookData | Serializer.Notebook type TaskCell = NotebookCell | NotebookCellData | Serializer.Cell +type RunmeTaskOptions = { + isNameGenerated?: boolean + filePath: string + command: string + notebook: TaskNotebook | Observable + cell: TaskCell | Observable + options: TaskOptions + runner: IRunner + runnerEnv: IRunnerEnvironment | undefined +} + export class RunmeTaskProvider implements TaskProvider { static execCount = 0 static id = 'runme' @@ -207,14 +218,14 @@ export class RunmeTaskProvider implements TaskProvider { } static async newRunmeProjectTask( - knownTask: Pick, + knownTask: Pick, options: TaskOptions = {}, token: CancellationToken, serializer: SerializerBase, runner: IRunner, runnerEnv?: IRunnerEnvironment, ): Promise { - const { id, name, documentPath } = knownTask + const { id, name, documentPath, isNameGenerated } = knownTask let mdBuffer: Uint8Array try { mdBuffer = await workspace.fs.readFile(Uri.parse(documentPath)) @@ -235,26 +246,42 @@ export class RunmeTaskProvider implements TaskProvider { }), ) - return this.newRunmeTask(documentPath, name, notebook, cell, options, runner, runnerEnv) + return this.newRunmeTask({ + isNameGenerated: isNameGenerated, + filePath: documentPath, + command: name, + notebook: notebook, + cell: cell, + options, + runner, + runnerEnv, + }) } - static async newRunmeTask( - filePath: string, - command: string, - notebookish: TaskNotebook | Observable, - cellish: TaskCell | Observable, - options: TaskOptions = {}, - runner: IRunner, - runnerEnv: IRunnerEnvironment | undefined, - ): Promise { + static async newRunmeTask({ + isNameGenerated, + filePath, + command, + notebook, + cell, + options = {}, + runner, + runnerEnv, + }: RunmeTaskOptions): Promise { const source = asWorkspaceRelativePath(filePath).relativePath const name = `${command}` - notebookish = !isObservable(notebookish) ? of(notebookish) : notebookish - cellish = !isObservable(cellish) ? of(cellish) : cellish + const notebookish = !isObservable(notebook) ? of(notebook) : notebook + const cellish = !isObservable(cell) ? of(cell) : cell const task = new Task( - { type: 'runme', name, command: name }, + { + type: 'runme', + name, + command: name, + fileUri: Uri.file(filePath), + isNameGenerated: isNameGenerated, + }, TaskScope.Workspace, name, source,