Skip to content

Commit

Permalink
Add workspace integration approach
Browse files Browse the repository at this point in the history
  • Loading branch information
pastuxso committed Jan 7, 2025
1 parent e7e8805 commit ab115ad
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 25 deletions.
17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@
},
"contributes": {
"commands": [
{
"command": "runme.workspaceInit",
"category": "Runme",
"title": "Attach Workspace"
},
{
"command": "runme.new",
"title": "Runme Notebook"
Expand Down Expand Up @@ -1155,6 +1160,12 @@
},
"views": {
"explorer": [
{
"id": "runme.workspaceNotebooks",
"type": "tree",
"name": "Workspace Notebooks (tree)",
"visibility": "visible"
},
{
"id": "runme.launcher",
"type": "tree",
Expand All @@ -1163,12 +1174,6 @@
}
],
"runme": [
{
"id": "runme.workspaceNotebooks",
"type": "tree",
"name": "Workspace Notebooks",
"visibility": "visible"
},
{
"id": "runme.cloud",
"type": "webview",
Expand Down
19 changes: 13 additions & 6 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ export class RunmeExtension {
protected serializer?: SerializerBase

async initialize(context: ExtensionContext) {
// Register the Runme file system provider as soon as possible
context.subscriptions.push(
workspace.registerFileSystemProvider('runmefs', new WorkspaceNotebooksFileSystem(), {
isReadonly: false,
}),
)

const kernel = new Kernel(context)
const grpcSerializer = kernel.hasExperimentEnabled('grpcSerializer')
const grpcServer = kernel.hasExperimentEnabled('grpcServer')
Expand Down Expand Up @@ -431,15 +438,15 @@ export class RunmeExtension {
)
},
),

commands.registerCommand('runme.workspaceInit', (_) => {
workspace.updateWorkspaceFolders(0, 0, {
uri: Uri.parse('runmefs://foo.com'),
name: 'Workspace Notebooks',
})
}),
RunmeExtension.registerCommand('runme.openCloudPanel', () =>
commands.executeCommand('workbench.view.extension.runme'),
),

workspace.registerFileSystemProvider('runmefs', new WorkspaceNotebooksFileSystem(), {
isReadonly: false,
}),

// Register a command to generate completions using foyle
RunmeExtension.registerCommand(
'runme.aiGenerate',
Expand Down
1 change: 1 addition & 0 deletions src/extension/messages/platformRequest/getAllWorkflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default async function getAllWorkflows() {
const result = await graphClient.query({
query: GetAllWorkflowsDocument,
variables: {
fileName: 'vscode-runme',
page: 1,
},
})
Expand Down
30 changes: 23 additions & 7 deletions src/extension/provider/workspaceNotebooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ export class WorkspaceNotebooks implements TreeDataProvider<WorkspaceNotebook>,
return Promise.resolve([])
}

const items = workflows.map((workflow) => {
const uri = Uri.parse(
`runmefs://github.com/${workflow.repository}/blob/${workflow.path}?id=${workflow.id}`,
)
const items = workflows.reduce((acc: { [key: string]: TreeItem[] }, workflow) => {
const [_owner, repository] = workflow.repository.split('/')
const uri = Uri.parse(`runmefs://foo.com/${repository}/${workflow.path}?q=${workflow.id}`)

const item: TreeItem = {
label: `${workflow.path}`,
Expand All @@ -36,10 +35,27 @@ export class WorkspaceNotebooks implements TreeDataProvider<WorkspaceNotebook>,
},
}

return item
})
const path = workflow.path
acc[path] = acc[path] || []
acc[path].push(item)
return acc
}, {})

return items
const sortedItems = Object.keys(items)
.sort((a, b) => {
const aHasSlash = a.includes('/')
const bHasSlash = b.includes('/')
if (aHasSlash && !bHasSlash) {
return -1
}
if (!aHasSlash && bHasSlash) {
return 1
}
return a.localeCompare(b)
})
.flatMap((path) => items[path])

return sortedItems
}

dispose(): void {
Expand Down
136 changes: 130 additions & 6 deletions src/extension/provider/workspaceNotebooksFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,38 @@ import {
} from 'vscode'

import getOneWorkflow from '../messages/platformRequest/getOneWorkflow'
import getAllWorkflows from '../messages/platformRequest/getAllWorkflows'

export type Workflow = {
owner: string
repository: string
path: string
id: string
}

export type TreeNode = {
path: string
fileType?: FileType
children?: TreeNode[]
}

export type TreeNodes = TreeNode[]
export type NodesMap = Record<string, string[]>

/**
* Handles the virtual file system runmefs://
*/
export default class WorkspaceNotebooksFileSystem implements FileSystemProvider {
private _onDidChangeFile: EventEmitter<FileChangeEvent[]> = new EventEmitter<FileChangeEvent[]>()
readonly onDidChangeFile: Event<FileChangeEvent[]> = this._onDidChangeFile.event

async readFile(uri: Uri): Promise<Uint8Array> {
// extraxt id from query string in uri
const id = uri.query.split('=')[1]
#notebooks: Workflow[] = []
#nodesMap: NodesMap = {}

async readFile(sourceUri: Uri): Promise<Uint8Array> {
const uri = sourceUri.with({ authority: 'foo.com' })
let id: string | undefined = uri.query.split('=')[1]
id = id || this.#notebooks.find((n) => `/${n.path}` === uri.path)?.id

if (!id) {
throw FileSystemError.FileNotFound(uri)
Expand Down Expand Up @@ -48,7 +72,40 @@ export default class WorkspaceNotebooksFileSystem implements FileSystemProvider
return new Disposable(() => {})
}

async stat(uri: Uri): Promise<FileStat> {
isFile(pathname: string): boolean {
const parts = pathname.split('/')
const lastPart = parts[parts.length - 1]
const hasExtension = lastPart && /\.[^/.]+$/.test(lastPart)
return Boolean(hasExtension)
}

isDir(pathname: string): boolean {
return !this.isFile(pathname)
}

async stat(sourceUri: Uri): Promise<FileStat> {
const uri = sourceUri.with({ authority: 'foo.com' })
const excludedPaths = [
'.vscode/tasks.json',
'.vscode/launch.json',
'.vscode/settings.json',
'.runme_bootstrap',
'.runme_bootstrap_demo',
]

if (excludedPaths.includes(uri.path)) {
throw FileSystemError.FileNotFound(uri)
}

if (this.#nodesMap[uri.path] || this.isDir(uri.path)) {
return {
type: FileType.Directory,
ctime: Date.now(),
mtime: Date.now(),
size: 0,
}
}

return {
type: FileType.File,
size: (await this.readFile(uri)).byteLength,
Expand All @@ -57,8 +114,75 @@ export default class WorkspaceNotebooksFileSystem implements FileSystemProvider
}
}

async readDirectory(_uri: Uri): Promise<[string, FileType][]> {
return []
async getMarkdownNotebooks() {
const response = await getAllWorkflows()
const data = response?.data?.workflows?.data || []
const notebooks = data
.map((notebook) => {
const [owner, repository] = notebook.repository.split('/')

return {
id: notebook.id,
path: `${repository}/${notebook.path}`,
repository: repository,
owner: owner,
}
})
.sort((a, b) => a.path.localeCompare(b.path))

return notebooks
}

getTreeNodes(notebooks: Workflow[]): NodesMap {
const nodes: NodesMap = {}
nodes['/'] = []

notebooks.forEach((notebook) => {
const parts = notebook.path.split('/')
let currentPath = ''

// This loop will process all parts of the path, no matter how deep
for (let i = 0; i < parts.length; i++) {
const prevPath = currentPath || '/'
currentPath = currentPath ? `${currentPath}/${parts[i]}` : `/${parts[i]}`

if (!nodes[prevPath]) {
nodes[prevPath] = []
}

if (!nodes[prevPath].includes(parts[i])) {
nodes[prevPath].push(parts[i])
}
}
})

return nodes
}

async readDirectory(sourceUri: Uri): Promise<[string, FileType][]> {
const uri = sourceUri.with({ authority: 'foo.com' })

if (!this.#notebooks.length) {
this.#notebooks = await this.getMarkdownNotebooks()
this.#nodesMap = this.getTreeNodes(this.#notebooks)
}

if (!this.#notebooks.length) {
return []
}

const children = this.#nodesMap[uri.path] || []

const isRoot = uri.path === '/'
return children.map((child) => {
if (isRoot) {
return [child, FileType.Directory]
}

const isDir = this.#nodesMap[`${uri.path}/${child}`] ? true : false

return [child, isDir ? FileType.Directory : FileType.File]
})
}

createDirectory(_uri: Uri): void {}
Expand Down

0 comments on commit ab115ad

Please sign in to comment.