Skip to content

Commit

Permalink
Merge pull request #14 from rainprotocol/2023-07-10-auto-compile [minor]
Browse files Browse the repository at this point in the history
auto compile configuration [minor]
  • Loading branch information
rouzwelt authored Jul 12, 2023
2 parents 79816cb + b3e7191 commit 1fa5631
Show file tree
Hide file tree
Showing 17 changed files with 383 additions and 65 deletions.
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,58 @@ It also includes an End-to-End test.

## Functionality

Rain Language Server works for rain files with `.rain`, `.rainlang` or `.rl` extentions as well as syntax highlighting for javascript/typescript [Tagged Template Literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) with using `rainlang()` as a tagged template literal function, example:
Rain Language Server works for rain files with `.rain` extentions as well as syntax highlighting for javascript/typescript [Tagged Template Literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) with using `rainlang()` as a tagged template literal function, example:
```typescript
// rainlang function is part of rainlang API, see: https://github.com/rainprotocol/rainlang
const myExp = rainlang`_: add(1 2)`
```

## Tutorial

### Configure Additional Subgraphs For Metas
### Configurations
Extension configuration are as follows applied to `user` (except auto compile) or `workspace` vscode json settings i.e. `settings.json`:
- `subgraphs`: By default rainlang will search through hardcoded [subgraphs](https://github.com/rainprotocol/meta/blob/master/src/subgraphBook.ts) to find specified contents of a meta hash, however, you can add more subgraph endpoint URLs. Specified subgraph URLs must be under `https://api.thegraph.com/subgraphs/name/` domain.
- `localMetas`: It is possible to set local metas by adding key/value pairs of meta hash and meta content bytes as hex string
- `autoCompile`: Providing a path to a json file that contains mappings (array) of dotrain files paths and expression names and output json files paths as objects to be compiled and written to their corresponding json files when an action (e.g. save) is triggered, an example of a mapping json content:
```json
[
{
"dotrain": "./path/to/dotrain1.rain",
"json": "./path/to/compiledDotrain1.json",
"expressions": [
"exp-1",
"exp-2"
]
},
{
"dotrain": "./path/to/dotrain12.rain",
"json": "./path/to/compiledDotrain2.json",
"expressions": [
"main"
]
}
]
```
Paths MUST be relative to working workspace ROOT directory starting with `./` in UNIX format (i.e. `/` as path seperator)
Please note that this feature (`autoCompile`) should ONLY be used per workspace i.e. `workspace` settings.json and not globaly on `user` settings.json.
<br>

By default rainlang will search through hardcoded [subgraphs](https://github.com/rainprotocol/meta/blob/master/src/subgraphBook.ts) to find specified meta of a meta hash, however, you can add more subgraph endpoint URLs by adding the following to the `user` or `workspace` vscode json settings i.e. `settings.json`:<br>
example:
```json
{
"rainlang.subgraphs": [
"https://api.thegraph.com/subgraphs/name/example1/example1",
"https://api.thegraph.com/subgraphs/name/example2/example2"
]
],
"rainlang.localMetas": {
"0xe4c000f3728f30e612b34e401529ce5266061cc1233dc54a6a89524929571d8f": "0x123456...",
"0x56ffc3fc82109c33f1e1544157a70144fc15e7c6e9ae9c65a636fd165b1bc51c": "0xabcdef..."
},
"rainlang.autoCompile": {
"onSave": "./path/to/mappings.json"
}
}
```
Specified subgraph URLs must be under `https://api.thegraph.com/subgraphs/name/` domain.
<br>

### Compilation
Expand Down
113 changes: 108 additions & 5 deletions client/src/browserClient.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import * as vscode from "vscode";
import { format } from "prettier/standalone";
import babelParser from "prettier/parser-babel";
import { LanguageClient, LanguageClientOptions } from "vscode-languageclient/browser";


let client: LanguageClient;

// this method is called when vs code is activated
export async function activate(context: vscode.ExtensionContext) {

// get the initial settings and pass them as initialzeOptions to server
const initSettings = vscode.workspace.getConfiguration("rainlang");

Expand All @@ -20,7 +19,7 @@ export async function activate(context: vscode.ExtensionContext) {
{ language: "typescript" }
],
synchronize: {},
initializationOptions: { opmeta: JSON.stringify(initSettings.opmeta) }
initializationOptions: JSON.stringify(initSettings)
};

// channel for rainlang compiler
Expand All @@ -44,11 +43,12 @@ export async function activate(context: vscode.ExtensionContext) {
}
);
if (!rainlangCompilerChannel) rainlangCompilerChannel = vscode.window.createOutputChannel(
"Rain Language Compiler", "json"
"Rain Language Compiler",
"json"
);
rainlangCompilerChannel.show(true);
if (result) rainlangCompilerChannel.appendLine(format(
JSON.stringify(result, null, 4),
JSON.stringify(result, null, 2),
{ parser: "json" }
));
else rainlangCompilerChannel.appendLine("undefined");
Expand All @@ -60,6 +60,102 @@ export async function activate(context: vscode.ExtensionContext) {
rainlangCompileHandler
));

// auto compile on save implementation
vscode.workspace.onDidSaveTextDocument(async e => {
const autoCompile = vscode.workspace.getConfiguration("rainlang.autoCompile");
if (typeof autoCompile.onSave === "string" && autoCompile.onSave) {
const workspaceRootUri = getCurrentWorkspaceDir();
if (workspaceRootUri) {
try {
const mappingFileUri = vscode.Uri.joinPath(
workspaceRootUri,
autoCompile.onSave
);
const content = JSON.parse(
(await vscode.workspace.fs.readFile(mappingFileUri)).toString()
);
if (Array.isArray(content) && content.length) {
const EXP_PATTERN = /^[a-z][0-9a-z-]*$/;
const JSON_PATH_PATTERN = /^(\.\/)(\.\/|\.\.\/|[^]*\/)*[^]+\.json$/;
const DOTRAIN_PATH_PATTERN = /^(\.\/)(\.\/|\.\.\/|[^]*\/)*[^]+\.rain$/;
const filesToCompile: {
dotrain: vscode.Uri,
json: vscode.Uri,
expressions: string[]
}[] = content?.map((v: any) => {
if (
typeof v.dotrain === "string"
&& DOTRAIN_PATH_PATTERN.test(v.dotrain)
&& typeof v.json === "string"
&& JSON_PATH_PATTERN.test(v.json)
&& Array.isArray(v.expressions)
&& v.expressions.length
&& v.expressions.every((name: any) =>
typeof name === "string"
&& EXP_PATTERN.test(name)
)
) {
try {
const dotrain = vscode.Uri.joinPath(
workspaceRootUri,
v.dotrain
);
const json = vscode.Uri.joinPath(
workspaceRootUri,
v.json
);

if (dotrain && json) {
return {
dotrain,
json,
expressions: v.expressions
};
}
else return undefined;
}
catch { return undefined; }
}
else return undefined;
})?.filter(v =>
v !== undefined && v.dotrain.toString() === e.uri.toString()
) ?? [];

if (filesToCompile.length) {
const workspaceEdit = new vscode.WorkspaceEdit();
for (let i = 0; i < filesToCompile.length; i++) {
const result = await vscode.commands.executeCommand(
"_compile",
e.languageId,
e.uri.toString(),
JSON.stringify(filesToCompile[i].expressions)
);
const contents: Uint8Array = Buffer.from(
format(
result
? JSON.stringify(result, null, 2)
: "failed to compile!",
{ parser: "json" }
)
);
workspaceEdit.createFile(
filesToCompile[i].json,
{ overwrite: true, contents }
);
}
vscode.workspace.applyEdit(workspaceEdit);
}
}
}
catch {
vscode.window.showErrorMessage(
"Failed to find mapping file or it contains invalid content"
);
}
}
}
});

// Create a worker. The worker main file implements the language server.
const serverMain = vscode.Uri.joinPath(context.extensionUri, "dist/browser/server.js");
const worker = new Worker(serverMain.toString(true));
Expand All @@ -79,3 +175,10 @@ export function deactivate(): Thenable<void> | undefined {
if (!client) return undefined;
return client.stop();
}

function getCurrentWorkspaceDir(): vscode.Uri | undefined {
const currentWorkspaceEditor = vscode.window.activeTextEditor?.document.uri;
return currentWorkspaceEditor
? vscode.workspace.getWorkspaceFolder(currentWorkspaceEditor)?.uri
: undefined;
}
120 changes: 110 additions & 10 deletions client/src/nodeClient.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import * as path from "path";
import { format } from "prettier";
import * as vscode from "vscode";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from "vscode-languageclient/node";
import { format } from "prettier";
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from "vscode-languageclient/node";


let client: LanguageClient;

export async function activate(context: vscode.ExtensionContext) {

// The server is implemented in node
const serverModule = context.asAbsolutePath(
path.join("dist", "node", "server.js")
Expand Down Expand Up @@ -41,11 +37,12 @@ export async function activate(context: vscode.ExtensionContext) {
}
);
if (!rainlangCompilerChannel) rainlangCompilerChannel = vscode.window.createOutputChannel(
"Rain Language Compiler", "json"
"Rain Language Compiler",
"json"
);
rainlangCompilerChannel.show(true);
if (result) rainlangCompilerChannel.appendLine(format(
JSON.stringify(result, null, 4),
JSON.stringify(result, null, 2),
{ parser: "json" }
));
else rainlangCompilerChannel.appendLine("undefined");
Expand All @@ -56,6 +53,102 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand("rainlang.compile", rainlangCompileHandler)
);

// auto compile on save implementation
vscode.workspace.onDidSaveTextDocument(async e => {
const autoCompile = vscode.workspace.getConfiguration("rainlang.autoCompile");
if (typeof autoCompile.onSave === "string" && autoCompile.onSave) {
const workspaceRootUri = getCurrentWorkspaceDir();
if (workspaceRootUri) {
try {
const mappingFileUri = vscode.Uri.joinPath(
workspaceRootUri,
autoCompile.onSave
);
const content = JSON.parse(
(await vscode.workspace.fs.readFile(mappingFileUri)).toString()
);
if (Array.isArray(content) && content.length) {
const EXP_PATTERN = /^[a-z][0-9a-z-]*$/;
const JSON_PATH_PATTERN = /^(\.\/)(\.\/|\.\.\/|[^]*\/)*[^]+\.json$/;
const DOTRAIN_PATH_PATTERN = /^(\.\/)(\.\/|\.\.\/|[^]*\/)*[^]+\.rain$/;
const filesToCompile: {
dotrain: vscode.Uri,
json: vscode.Uri,
expressions: string[]
}[] = content?.map((v: any) => {
if (
typeof v.dotrain === "string"
&& DOTRAIN_PATH_PATTERN.test(v.dotrain)
&& typeof v.json === "string"
&& JSON_PATH_PATTERN.test(v.json)
&& Array.isArray(v.expressions)
&& v.expressions.length
&& v.expressions.every((name: any) =>
typeof name === "string"
&& EXP_PATTERN.test(name)
)
) {
try {
const dotrain = vscode.Uri.joinPath(
workspaceRootUri,
v.dotrain
);
const json = vscode.Uri.joinPath(
workspaceRootUri,
v.json
);

if (dotrain && json) {
return {
dotrain,
json,
expressions: v.expressions
};
}
else return undefined;
}
catch { return undefined; }
}
else return undefined;
})?.filter(v =>
v !== undefined && v.dotrain.toString() === e.uri.toString()
) ?? [];

if (filesToCompile.length) {
const workspaceEdit = new vscode.WorkspaceEdit();
for (let i = 0; i < filesToCompile.length; i++) {
const result = await vscode.commands.executeCommand(
"_compile",
e.languageId,
e.uri.toString(),
filesToCompile[i].expressions
);
const contents: Uint8Array = Buffer.from(
format(
result
? JSON.stringify(result, null, 2)
: "failed to compile!",
{ parser: "json" }
)
);
workspaceEdit.createFile(
filesToCompile[i].json,
{ overwrite: true, contents }
);
}
vscode.workspace.applyEdit(workspaceEdit);
}
}
}
catch {
vscode.window.showErrorMessage(
"Failed to find mapping file or it contains invalid content"
);
}
}
}
});

// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
const serverOptions: ServerOptions = {
Expand All @@ -80,7 +173,7 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.workspace.createFileSystemWatcher("**/.clientrc")
]
},
initializationOptions: { opmeta: initSettings.opmeta }
initializationOptions: initSettings
};

// const myProvider = new (class implements vscode.InlayHintsProvider {
Expand Down Expand Up @@ -117,3 +210,10 @@ export function deactivate(): Thenable<void> | undefined {
if (!client) return undefined;
return client.stop();
}

function getCurrentWorkspaceDir(): vscode.Uri | undefined {
const currentWorkspaceEditor = vscode.window.activeTextEditor?.document.uri;
return currentWorkspaceEditor
? vscode.workspace.getWorkspaceFolder(currentWorkspaceEditor)?.uri
: undefined;
}
Loading

0 comments on commit 1fa5631

Please sign in to comment.