Skip to content

Commit

Permalink
Refactor New Runme Notebooks Panel to use Runme Tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
pastuxso committed Nov 7, 2024
1 parent 5c97dae commit d63f82d
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 170 deletions.
12 changes: 3 additions & 9 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
14 changes: 7 additions & 7 deletions src/extension/provider/codelens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
166 changes: 27 additions & 139 deletions src/extension/provider/launcherBeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -44,14 +26,6 @@ import { OpenFileOptions, RunmeFile, RunmeTreeProvider } from './launcher'
export const GLOB_PATTERN = '**/*.{md,mdr,mdx}'
const logger = getLogger('LauncherBeta')

type LoadStream = ServerStreamingCall<LoadRequest, LoadResponse>

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
Expand All @@ -62,35 +36,15 @@ let sauceCount = 0
export class RunmeLauncherProvider implements RunmeTreeProvider {
#disposables: Disposable[] = []
private allowUnnamed = false
private tasks: Promise<ProjectTask[]>
private ready: ReadyPromise
private client: ProjectServiceClient | undefined
private defaultItemState = TreeItemCollapsibleState.Expanded
private serverReadyListener: Disposable | undefined
private defaultItemState = TreeItemCollapsibleState.Collapsed
private _onDidChangeTreeData = new EventEmitter<RunmeFile | undefined>()

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)),
Expand Down Expand Up @@ -152,27 +106,30 @@ export class RunmeLauncherProvider implements RunmeTreeProvider {
}

async getChildren(element?: RunmeFile | undefined): Promise<RunmeFile[]> {
const allTasks = await this.tasks
const tasks = await vscodeTasks.fetchTasks({ type: 'runme' })

if (!element) {
/**
* we need to tweak the folder name to force VS Code re-render the tree view
* 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<RunmeFile[]> {
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
}
Expand All @@ -195,16 +152,21 @@ export class RunmeLauncherProvider implements RunmeTreeProvider {
return foundTasks
}

async getCells(tasks: LoadEventFoundTask[], element: RunmeFile): Promise<RunmeFile[]> {
async getCells(tasks: Task[], element: RunmeFile): Promise<RunmeFile[]> {
const foundTasks: RunmeFile[] = []

let mdBuffer: Uint8Array
let prevFile: string | undefined
let notebook: Observable<NotebookData> | 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
}
Expand All @@ -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<TaskNotebook>(notebookish as any)
const cellish = !isObservable(cell) ? of(cell) : cell
const taskCell = await firstValueFrom<TaskCell>(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')
Expand Down Expand Up @@ -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<ProjectTask[]> {
if (!workspace.workspaceFolders?.length) {
return of([])
}

const separator = workspace.workspaceFolders[0].uri.fsPath.indexOf('/') > -1 ? '/' : '\\'

const requests = (workspace.workspaceFolders ?? []).map((folder) => {
return <LoadRequest>{
kind: {
oneofKind: 'directory',
directory: {
path: workspace.asRelativePath(folder.uri),
skipGitignore: false,
ignoreFilePatterns: [],
skipRepoLookupUpward: false,
},
},
identity: RunmeIdentity.ALL,
}
})

const task$ = new Observable<ProjectTask>((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
Expand Down
57 changes: 42 additions & 15 deletions src/extension/provider/runmeTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TaskNotebook>
cell: TaskCell | Observable<TaskCell>
options: TaskOptions
runner: IRunner
runnerEnv: IRunnerEnvironment | undefined
}

export class RunmeTaskProvider implements TaskProvider {
static execCount = 0
static id = 'runme'
Expand Down Expand Up @@ -207,14 +218,14 @@ export class RunmeTaskProvider implements TaskProvider {
}

static async newRunmeProjectTask(
knownTask: Pick<ProjectTask, 'id' | 'name' | 'documentPath'>,
knownTask: Pick<ProjectTask, 'id' | 'name' | 'documentPath' | 'isNameGenerated'>,
options: TaskOptions = {},
token: CancellationToken,
serializer: SerializerBase,
runner: IRunner,
runnerEnv?: IRunnerEnvironment,
): Promise<Task> {
const { id, name, documentPath } = knownTask
const { id, name, documentPath, isNameGenerated } = knownTask
let mdBuffer: Uint8Array
try {
mdBuffer = await workspace.fs.readFile(Uri.parse(documentPath))
Expand All @@ -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<TaskNotebook>,
cellish: TaskCell | Observable<TaskCell>,
options: TaskOptions = {},
runner: IRunner,
runnerEnv: IRunnerEnvironment | undefined,
): Promise<Task> {
static async newRunmeTask({
isNameGenerated,
filePath,
command,
notebook,
cell,
options = {},
runner,
runnerEnv,
}: RunmeTaskOptions): Promise<Task> {
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,
Expand Down

0 comments on commit d63f82d

Please sign in to comment.