Skip to content

Commit

Permalink
Proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
joyceerhl committed Mar 27, 2022
1 parent 9764d71 commit 1c5e631
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 15 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
"onCommand:vscode-graphql.isDebugging",
"onCommand:vscode-graphql.contentProvider",
"onLanguage:graphql",
"onCommand:vscode-graphql.createNotebook",
"onNotebook:gqlnb",
"workspaceContains:**/.graphqlrc",
"workspaceContains:**/.graphqlrc.{json,yaml,yml,js,ts,toml}",
"workspaceContains:**/graphql.config.{json,yaml,yml,js,ts,toml}",
Expand Down Expand Up @@ -207,6 +209,32 @@
{
"command": "vscode-graphql.contentProvider",
"title": "VSCode GraphQL: Execute GraphQL Operations"
},
{
"command": "vscode-graphql.createNotebook",
"title": "Create New GraphQL Notebook",
"shortTitle": "GraphQL Notebook",
"category": "GraphQL"
}
],
"menus": {
"file/newFile": [
{
"command": "vscode-graphql.createNotebook",
"group": "notebook"
}
]
},
"notebooks": [
{
"type": "gqlnb",
"displayName": "GraphQL Notebook",
"priority": "default",
"selector": [
{
"filenamePattern": "*.gqlnb"
}
]
}
]
},
Expand Down
27 changes: 15 additions & 12 deletions src/client/graphql-content-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class GraphQLContentProvider implements TextDocumentContentProvider {
private outputChannel: OutputChannel
private networkHelper: NetworkHelper
private sourceHelper: SourceHelper
private panel: WebviewPanel
private panel: WebviewPanel | undefined
private rootDir: WorkspaceFolder | undefined
private literal: ExtractedTemplateLiteral

Expand All @@ -45,6 +45,7 @@ export class GraphQLContentProvider implements TextDocumentContentProvider {
}

updatePanel() {
if (!this.panel) return
this.panel.webview.html = this.html
}

Expand Down Expand Up @@ -93,7 +94,7 @@ export class GraphQLContentProvider implements TextDocumentContentProvider {
uri: Uri,
outputChannel: OutputChannel,
literal: ExtractedTemplateLiteral,
panel: WebviewPanel,
panel?: WebviewPanel,
) {
this.uri = uri
this.outputChannel = outputChannel
Expand All @@ -105,15 +106,17 @@ export class GraphQLContentProvider implements TextDocumentContentProvider {
this.panel = panel
this.rootDir = workspace.getWorkspaceFolder(Uri.file(literal.uri))
this.literal = literal
this.panel.webview.options = {
enableScripts: true,
if (this.panel) {
this.panel.webview.options = {
enableScripts: true,
}
this.loadProvider()
.then()
.catch(err => {
this.html = err.toString()
})
}

this.loadProvider()
.then()
.catch(err => {
this.html = err.toString()
})
}
validUrlFromSchema(pathOrUrl: string) {
return Boolean(pathOrUrl.match(/^https?:\/\//g))
Expand Down Expand Up @@ -175,7 +178,7 @@ export class GraphQLContentProvider implements TextDocumentContentProvider {
const endpointName = await this.getEndpointName(endpointNames)
return endpoints[endpointName] || endpoints.default
}
async loadProvider() {
async loadProvider(updateCallback?: (data: string, operation: string) => void) {
try {
const rootDir = workspace.getWorkspaceFolder(Uri.file(this.literal.uri))
if (!rootDir) {
Expand All @@ -197,15 +200,15 @@ export class GraphQLContentProvider implements TextDocumentContentProvider {
},
})

const updateCallback = (data: string, operation: string) => {
updateCallback = updateCallback ?? ((data: string, operation: string) => {
if (operation === "subscription") {
this.html = `<pre>${escapeHtml(data)}</pre>` + this.html
} else {
this.html += `<pre>${escapeHtml(data)}</pre>`
}
this.update(this.uri)
this.updatePanel()
}
})

if (variableDefinitionNodes.length > 0) {
const variables = await this.getVariablesFromUser(
Expand Down
Empty file.
48 changes: 48 additions & 0 deletions src/client/notebook-serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { TextDecoder, TextEncoder } from 'util';
import * as vscode from 'vscode';

export class NotebookSerializer implements vscode.NotebookSerializer {
createNew(): vscode.NotebookData {
const language = 'graphql';
const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', language);
return new vscode.NotebookData([cell]);
}

serializeNotebook(data: vscode.NotebookData, token?: vscode.CancellationToken): Uint8Array {
const cells = data.cells.map((cell) => {
return { code: cell.value, kind: cell.kind === vscode.NotebookCellKind.Markup ? 'markdown' : 'code' };
});
return new TextEncoder().encode(JSON.stringify({ cells }));
}

deserializeNotebook(content: Uint8Array, token: vscode.CancellationToken): vscode.NotebookData {
const stringified = content.length === 0 ? new TextDecoder().decode(this.serializeNotebook(this.createNew())) : new TextDecoder().decode(content);
const data = JSON.parse(stringified);
if (!('cells' in data)) {
throw new Error('Unable to parse provided notebook content, missing required `cells` property.');
}
if (!Array.isArray(data.cells)) {
throw new Error('Unable to parse provided notebook contents, `cells` is not an array.');
}
const cells: (vscode.NotebookCellData | undefined)[] = data.cells.map((cell: unknown) => {
if (typeof cell !== 'object' || cell === null) {
return undefined;
}
if (cell.hasOwnProperty('code') && cell.hasOwnProperty('kind') && 'kind' in cell) {
const graphqlCell = cell as unknown as { code: string, kind: 'markdown' | 'code' };
return new vscode.NotebookCellData(
graphqlCell.kind === 'code' ? vscode.NotebookCellKind.Code : vscode.NotebookCellKind.Markup,
graphqlCell.code,
graphqlCell.kind === 'code' ? 'graphql' : 'markdown',
);
}
});
const cellData: vscode.NotebookCellData[] = [];
for (const cell of cells) {
if (cell !== undefined) {
cellData.push(cell);
}
}
return new vscode.NotebookData(cellData);
}
}
61 changes: 60 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import {
languages,
Uri,
ViewColumn,
notebooks,
NotebookCellOutput,
NotebookCellOutputItem,
NotebookCellExecution,
} from "vscode"
import {
LanguageClient,
Expand All @@ -22,8 +26,9 @@ import statusBarItem, { initStatusBar } from "./status"

import { GraphQLContentProvider } from "./client/graphql-content-provider"
import { GraphQLCodeLensProvider } from "./client/graphql-codelens-provider"
import { ExtractedTemplateLiteral } from "./client/source-helper"
import { ExtractedTemplateLiteral, SourceHelper } from "./client/source-helper"
import { CustomInitializationFailedHandler } from "./CustomInitializationFailedHandler"
import { NotebookSerializer } from "./client/notebook-serializer"

function getConfig() {
return workspace.getConfiguration(
Expand Down Expand Up @@ -121,6 +126,60 @@ export function activate(context: ExtensionContext) {
)
context.subscriptions.push(commandShowOutputChannel)

const serializer = new NotebookSerializer()
context.subscriptions.push(workspace.registerNotebookSerializer('gqlnb', serializer))

const commandCreateNotebook = commands.registerCommand('vscode-graphql.createNotebook', async () => {
const data = serializer.createNew()
const notebookDocument = await workspace.openNotebookDocument('gqlnb', data)
await commands.executeCommand('vscode.openWith', notebookDocument.uri, 'gqlnb')
})
context.subscriptions.push(commandCreateNotebook)

async function replaceOutput(task: NotebookCellExecution, jsonData: string) {
const parsed = JSON.parse(jsonData);
const stringified = JSON.stringify(parsed['data'], undefined, 4);
const data = Buffer.from(stringified);
const item = new NotebookCellOutputItem(data, 'text/x-json');
const output = new NotebookCellOutput([item]);
await task.replaceOutput(output);
}

notebooks.createNotebookController('GraphQL', 'gqlnb', 'GraphQL', async (cells, notebook, controller) => {
const sourceHelper = new SourceHelper(outputChannel);
for (const cell of cells) {
const literals = sourceHelper.extractAllTemplateLiterals(cell.document);
for (const literal of literals) {
const task = controller.createNotebookCellExecution(cell);
task.start(Date.now());
let success = false
try {
const uri = Uri.parse("graphql://authority/graphql")

const contentProvider = new GraphQLContentProvider(
uri,
outputChannel,
literal,
)

await contentProvider.loadProvider(async (data, operation) => {
if (operation === "subscription") { // TODO: how do we know when a subscription has finished?
const item = new NotebookCellOutputItem(Buffer.from(data), 'text/x-json');
await task.appendOutputItems(item, cell.outputs[cell.outputs.length - 1]);
} else {
success = true
await replaceOutput(task, data);
task.end(success, Date.now());
}
});
} catch (e) {
success = false
task.end(success, Date.now())
}
}
}
});

// Manage Status Bar
context.subscriptions.push(statusBarItem)
client.onReady().then(() => {
Expand Down

0 comments on commit 1c5e631

Please sign in to comment.