Skip to content

Commit 20d6d02

Browse files
Add command to create interface files (#90)
1 parent ca34f82 commit 20d6d02

File tree

6 files changed

+158
-1
lines changed

6 files changed

+158
-1
lines changed

client/src/commands.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as fs from 'fs'
2+
import { window } from 'vscode';
3+
import { LanguageClient, RequestType } from 'vscode-languageclient/node';
4+
5+
interface CreateInterfaceRequestParams {
6+
uri: string;
7+
};
8+
9+
let createInterfaceRequest = new RequestType<CreateInterfaceRequestParams, string, void>("rescript-vscode.create_interface");
10+
11+
export const createInterface = (client: LanguageClient) => {
12+
if (!client) {
13+
return window.showInformationMessage('Language server not running');
14+
}
15+
16+
const editor = window.activeTextEditor;
17+
18+
if (!editor) {
19+
return window.showInformationMessage('No active editor');
20+
}
21+
22+
if (fs.existsSync(editor.document.uri.fsPath + 'i')) {
23+
return window.showInformationMessage('Interface file already exists');
24+
}
25+
26+
client.sendRequest(createInterfaceRequest, {
27+
uri: editor.document.uri.toString(),
28+
})
29+
};

client/src/extension.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as path from "path";
2-
import { workspace, ExtensionContext } from "vscode";
2+
import { workspace, ExtensionContext, commands } from "vscode";
33

44
import {
55
LanguageClient,
@@ -8,6 +8,8 @@ import {
88
TransportKind,
99
} from "vscode-languageclient/node";
1010

11+
import * as customCommands from './commands';
12+
1113
let client: LanguageClient;
1214

1315
// let taskProvider = tasks.registerTaskProvider('Run ReScript build', {
@@ -96,6 +98,11 @@ export function activate(context: ExtensionContext) {
9698
clientOptions
9799
);
98100

101+
// Register custom commands
102+
commands.registerCommand('rescript-vscode.create_interface', () => {
103+
customCommands.createInterface(client);
104+
});
105+
99106
// Start the client. This will also launch the server
100107
client.start();
101108
}

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434
"url": "https://raw.githubusercontent.com/rescript-lang/rescript-compiler/master/docs/docson/build-schema.json"
3535
}
3636
],
37+
"commands": [
38+
{
39+
"command": "rescript-vscode.create_interface",
40+
"title": "ReScript: Create an interface file for this implementation file."
41+
}
42+
],
3743
"snippets": [
3844
{
3945
"language": "rescript",

server/src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ export let bsbNodePartialPath = path.join("node_modules", ".bin", "bsb");
3939

4040
export let bsbLock = ".bsb.lock";
4141
export let bsconfigPartialPath = "bsconfig.json";
42+
export let compilerDirPartialPath = path.join("lib", "bs");
4243
export let compilerLogPartialPath = path.join("lib", "bs", ".compiler.log");
4344
export let resExt = ".res";
4445
export let resiExt = ".resi";
46+
export let cmiExt = ".cmi";
4547
export let startBuildAction = "Start Build";

server/src/server.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ let projectsFiles: Map<
4242
// will be properly defined later depending on the mode (stdio/node-rpc)
4343
let send: (msg: m.Message) => void = (_) => { };
4444

45+
interface CreateInterfaceRequestParams {
46+
uri: string;
47+
};
48+
49+
let createInterfaceRequest = new v.RequestType<CreateInterfaceRequestParams, string, void>("rescript-vscode.create_interface");
50+
4551
let sendUpdatedDiagnostics = () => {
4652
projectsFiles.forEach(({ filesWithDiagnostics }, projectRootPath) => {
4753
let content = fs.readFileSync(
@@ -514,6 +520,82 @@ function onMessage(msg: m.Message) {
514520
}
515521
}
516522
}
523+
} else if (msg.method === createInterfaceRequest.method) {
524+
let params = msg.params as CreateInterfaceRequestParams;
525+
let extension = path.extname(params.uri);
526+
let filePath = fileURLToPath(params.uri);
527+
let bscNativePath = utils.findBscNativeOfFile(filePath);
528+
let projDir = utils.findProjectRootOfFile(filePath);
529+
530+
if (bscNativePath === null || projDir === null) {
531+
let params: p.ShowMessageParams = {
532+
type: p.MessageType.Error,
533+
message: `Cannot find a nearby bsc.exe to generate the interface file.`,
534+
};
535+
536+
let response: m.NotificationMessage = {
537+
jsonrpc: c.jsonrpcVersion,
538+
method: "window/showMessage",
539+
params: params,
540+
};
541+
542+
send(response);
543+
} else if (extension !== c.resExt) {
544+
let params: p.ShowMessageParams = {
545+
type: p.MessageType.Error,
546+
message: `Not a ${c.resExt} file. Cannot create an interface for it.`,
547+
};
548+
549+
let response: m.NotificationMessage = {
550+
jsonrpc: c.jsonrpcVersion,
551+
method: "window/showMessage",
552+
params: params,
553+
};
554+
555+
send(response);
556+
} else {
557+
let cmiPartialPath = utils.replaceFileExtension(filePath.split(projDir)[1], c.cmiExt);
558+
let cmiPath = path.join(projDir, c.compilerDirPartialPath, cmiPartialPath);
559+
let cmiAvailable = fs.existsSync(cmiPath);
560+
561+
if (!cmiAvailable) {
562+
let params: p.ShowMessageParams = {
563+
type: p.MessageType.Error,
564+
message: `No compiled interface file found. Please compile your project first.`
565+
};
566+
567+
let response: m.NotificationMessage = {
568+
jsonrpc: c.jsonrpcVersion,
569+
method: "window/showMessage",
570+
params,
571+
};
572+
573+
send(response);
574+
} else {
575+
let intfResult = utils.createInterfaceFileUsingValidBscExePath(filePath, cmiPath, bscNativePath)
576+
577+
if (intfResult.kind === "success") {
578+
let response: m.ResponseMessage = {
579+
jsonrpc: c.jsonrpcVersion,
580+
id: msg.id,
581+
result: intfResult.result,
582+
};
583+
584+
send(response);
585+
} else {
586+
let response: m.ResponseMessage = {
587+
jsonrpc: c.jsonrpcVersion,
588+
id: msg.id,
589+
error: {
590+
code: m.ErrorCodes.InternalError,
591+
message: "Unable to create interface file."
592+
}
593+
};
594+
595+
send(response);
596+
}
597+
}
598+
}
517599
} else {
518600
let response: m.ResponseMessage = {
519601
jsonrpc: c.jsonrpcVersion,

server/src/utils.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,37 @@ export let runAnalysisAfterSanityCheck = (
144144
return JSON.parse(stdout.toString());
145145
};
146146

147+
export let replaceFileExtension = (filePath: string, ext: string): string => {
148+
let name = path.basename(filePath, path.extname(filePath));
149+
return path.format({ dir: path.dirname(filePath), name, ext })
150+
};
151+
152+
export let createInterfaceFileUsingValidBscExePath = (
153+
filePath: string,
154+
cmiPath: string,
155+
bscExePath: p.DocumentUri
156+
): execResult => {
157+
try {
158+
let resiString = childProcess.execFileSync(
159+
bscExePath,
160+
["-color", "never", cmiPath]
161+
);
162+
163+
let resiPath = replaceFileExtension(filePath, c.resiExt)
164+
fs.writeFileSync(resiPath, resiString, { encoding: "utf-8"});
165+
166+
return {
167+
kind: "success",
168+
result: "Interface successfully created.",
169+
};
170+
} catch (e) {
171+
return {
172+
kind: "error",
173+
error: e.message,
174+
};
175+
}
176+
};
177+
147178
export let runBuildWatcherUsingValidBuildPath = (
148179
buildPath: p.DocumentUri,
149180
isRescript: boolean,

0 commit comments

Comments
 (0)