Skip to content

Commit

Permalink
✨ feat: Exp support for macOS using a standalone WS server
Browse files Browse the repository at this point in the history
  • Loading branch information
Snowflyt committed Jan 27, 2024
1 parent def475e commit 486a54d
Show file tree
Hide file tree
Showing 15 changed files with 424 additions and 63 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@types/node": "^20.11.6",
"@types/prettier": "2.7.3",
"@types/rangy": "^0.0.38",
"@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"@vitest/coverage-v8": "^1.2.1",
Expand Down Expand Up @@ -91,6 +92,7 @@
"tsx": "^4.7.0",
"typescript": "^5.3.3",
"typroof": "^0.2.5",
"vitest": "^1.2.1"
"vitest": "^1.2.1",
"ws": "^8.16.0"
}
}
54 changes: 36 additions & 18 deletions rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,40 @@ import typescript from "@rollup/plugin-typescript";
import { defineConfig } from "rollup";
import postcss from "rollup-plugin-postcss";

export default defineConfig({
input: "src/index.ts",
output: {
file: "dist/index.js",
format: "iife",
export default defineConfig([
{
input: "src/index.ts",
output: {
file: "dist/index.js",
format: "iife",
},
plugins: [
typescript({
tsconfig: "./tsconfig.build.json",
}),
nodeResolve({
extensions: [".js", ".jsx", ".ts", ".tsx"],
}),
commonjs(),
postcss({
inject: true,
}),
],
},
plugins: [
typescript({
tsconfig: "./tsconfig.build.json",
}),
nodeResolve({
extensions: [".js", ".jsx", ".ts", ".tsx"],
}),
commonjs(),
postcss({
inject: true,
}),
],
});
{
input: "src/mac-server.ts",
output: {
file: "dist/mac-server.cjs",
format: "cjs",
},
plugins: [
typescript({
tsconfig: "./tsconfig.build.json",
}),
nodeResolve({
extensions: [".js", ".jsx", ".ts", ".tsx"],
}),
commonjs(),
],
},
]);
2 changes: 1 addition & 1 deletion src/client/general-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ export const createClient = <
*/
const _send = (data: Message) => {
const dataString = JSON.stringify(data);
const contentLength = Buffer.byteLength(dataString, "utf8");
const contentLength = new TextEncoder().encode(dataString).length;
const rpcString = `Content-Length: ${contentLength}\r\n\r\n${dataString}`;
server.send(rpcString);
};
Expand Down
4 changes: 3 additions & 1 deletion src/components/CopilotIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ const CopilotIcon: FC<CopilotIconProps> = ({ status, textColor }) => {
</div>
);

const copilotIconPosixPathname = path.posix.join(
let copilotIconPosixPathname = path.posix.join(
...(status === "Normal" ? COPILOT_ICON_PATHNAME.NORMAL : COPILOT_ICON_PATHNAME.WARNING).split(
path.sep,
),
);
if (!(File as ExtendedFileConstructor).isWin && !copilotIconPosixPathname.startsWith("/"))
copilotIconPosixPathname = "/" + copilotIconPosixPathname;

return (
<div
Expand Down
4 changes: 3 additions & 1 deletion src/components/SuggestionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ const SuggestionPanel: FC<SuggestionPanelProps> = ({ text, textColor = "gray", x
(x - File.editor!.writingArea.getBoundingClientRect().left) -
30;

const copilotIconPosixPathname = path.posix.join(...COPILOT_ICON_PATHNAME.NORMAL.split(path.sep));
let copilotIconPosixPathname = path.posix.join(...COPILOT_ICON_PATHNAME.NORMAL.split(path.sep));
if (!File.isWin && !copilotIconPosixPathname.startsWith("/"))
copilotIconPosixPathname = "/" + copilotIconPosixPathname;

// Calculate actual width after mount, and adjust position
useEffect(() => {
Expand Down
9 changes: 9 additions & 0 deletions src/errors/PlatformError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Error thrown when a platform is not supported.
*/
export class PlatformError extends Error {
constructor(message: string) {
super(message);
this.name = "PlatformError";
}
}
1 change: 1 addition & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PlatformError } from "./PlatformError";
88 changes: 88 additions & 0 deletions src/mac-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { fork } from "child_process";
import net from "net";

import { WebSocketServer } from "ws";

import { wrapNodeChildProcess } from "./utils/server";

import type { ChildProcessWithoutNullStreams } from "child_process";

if (!process.argv[2] || !process.argv[3]) {
console.log("Usage: node mac-server.cjs <port> <lsp-node-module-path>");
process.exit(1);
}

const port = Number.parseInt(process.argv[2]);
if (Number.isNaN(port)) {
console.log(`Invalid port "${process.argv[2]}"`);
process.exit(1);
}

console.log("Process PID:", process.pid);

const server = wrapNodeChildProcess(
fork(process.argv[3], [], { silent: true }) as ChildProcessWithoutNullStreams,
);
console.log("Copilot LSP server started. PID:", server.pid);

const startWebSocketServer = () => {
const wss = new WebSocketServer({ port: port });

wss.on("connection", (ws) => {
console.log(`➕➕ Connection (${wss.clients.size})`);

ws.once("close", () => {
console.log("🚨 WebSocket Server shutting down...");
wss.close();
process.exit(0);
});

ws.on("message", (data) => {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const payload = data.toString("utf-8");
console.debug("📥", payload);
server.send(payload);
});

server.onMessage((message) => {
console.debug("📤", message);
ws.send(message);
});
});

console.log(`✅ WebSocket Server listening on ws://localhost:${port}`);

const cleanupServer = (() => {
let called = false;
return () => {
if (called) return;
called = true;
console.log("🚨 WebSocket Server shutting down...");
wss.close((err) => {
if (err) console.error(err);
process.exit(0);
});
};
})();

process.on("exit", cleanupServer);
process.on("SIGINT", cleanupServer);
process.on("SIGTERM", cleanupServer);
process.on("SIGUSR1", cleanupServer);
process.on("SIGUSR2", cleanupServer);
process.on("uncaughtException", cleanupServer);
};

const testServer = net.createServer();
testServer.once("error", (err) => {
if ((err as unknown as { code: string }).code === "EADDRINUSE") {
console.error(`🚨 Port ${port} is busy`);
process.exit(1);
}
});

testServer.once("listening", () => {
testServer.close(startWebSocketServer);
});

testServer.listen(port);
32 changes: 15 additions & 17 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,7 @@ import "./styles.scss";

import type { Completion } from "./client";

const server = forkNode(path.join(PLUGIN_DIR, "language-server", "agent.cjs"));

logger.info("Copilot plugin activated. Version:", VERSION);
logger.debug("Copilot LSP server started. PID:", server.pid);

/**
* Copilot LSP client.
*/
const copilot = createCopilotClient(server, { logging: "debug" });
setGlobalVar("copilot", copilot);

/**
* Fake temporary workspace folder, only used when no folder is opened.
Expand All @@ -44,10 +35,18 @@ const FAKE_TEMP_WORKSPACE_FOLDER = File.isWin
: "/home/fakeuser/faketyporacopilotworkspace";
const FAKE_TEMP_FILENAME = "typora-copilot-fake-markdown.md";

/**
* Main function.
*/
const main = async () => {
(async () => {
const server = await forkNode(path.join(PLUGIN_DIR, "language-server", "agent.cjs"));
logger.debug("Copilot LSP server started. PID:", server.pid);

/**
* Copilot LSP client.
*/
const copilot = createCopilotClient(server, { logging: "debug" });
setGlobalVar("copilot", copilot);

await waitUntilEditorInitialized();

/*********************
* Utility functions *
*********************/
Expand Down Expand Up @@ -716,7 +715,6 @@ const main = async () => {
// Invoke callback
void onChangeMarkdown(newMarkdown, oldMarkdown);
});
};

// Execute `main` function until Typora editor is initialized
void waitUntilEditorInitialized().then(main);
})().catch((err) => {
throw err;
});
Loading

0 comments on commit 486a54d

Please sign in to comment.