LSP working with Python!!

This commit is contained in:
Radon Rosborough 2020-06-23 14:51:43 -06:00
parent fc9cbbc7c6
commit 4425f31b88
7 changed files with 1473 additions and 11 deletions

View File

@ -1,8 +1,10 @@
import { ChildProcess, spawn } from "child_process";
import * as path from "path"; import * as path from "path";
import * as WebSocket from "ws"; import * as WebSocket from "ws";
import * as pty from "node-pty"; import * as pty from "node-pty";
import { IPty } from "node-pty"; import { IPty } from "node-pty";
import * as rpc from "vscode-jsonrpc";
import { v4 as getUUID } from "uuid"; import { v4 as getUUID } from "uuid";
import { PRIVILEGED } from "./config"; import { PRIVILEGED } from "./config";
@ -15,6 +17,11 @@ export class Session {
code: string; code: string;
config: LangConfig; config: LangConfig;
term: { pty: IPty | null; live: boolean }; term: { pty: IPty | null; live: boolean };
lsp: {
proc: ChildProcess;
reader: rpc.StreamMessageReader;
writer: rpc.StreamMessageWriter;
} | null;
ws: WebSocket; ws: WebSocket;
homedir: string | null; homedir: string | null;
uid: number | null; uid: number | null;
@ -28,6 +35,7 @@ export class Session {
this.ws = ws; this.ws = ws;
this.config = langs[lang]; this.config = langs[lang];
this.term = { pty: null, live: false }; this.term = { pty: null, live: false };
this.lsp = null;
this.code = ""; this.code = "";
this.homedir = null; this.homedir = null;
this.uid = null; this.uid = null;
@ -83,6 +91,13 @@ export class Session {
this.run(); this.run();
} }
break; break;
case "lspInput":
if (!this.lsp) {
this.log(`Got LSP input before language server was started`);
} else {
this.lsp.writer.write(msg.input);
}
break;
default: default:
this.log(`Got unknown message type: ${msg.event}`); this.log(`Got unknown message type: ${msg.event}`);
break; break;
@ -103,6 +118,7 @@ export class Session {
alwaysCreate, alwaysCreate,
compile, compile,
run, run,
lsp,
hacks, hacks,
} = this.config; } = this.config;
if (this.term.pty) { if (this.term.pty) {
@ -210,6 +226,31 @@ export class Session {
} }
} }
}); });
if (lsp && this.lsp === null) {
const lspArgs = PRIVILEGED
? [
"/home/docker/src/system/out/riju-system-privileged",
"spawn",
`${this.uid}`,
`${this.uuid}`,
"bash",
"-c",
lsp,
]
: ["bash", "-c", lsp];
const proc = spawn(lspArgs[0], lspArgs.slice(1), {
env: getEnv(this.uuid),
});
this.lsp = {
proc,
reader: new rpc.StreamMessageReader(proc.stdout),
writer: new rpc.StreamMessageWriter(proc.stdin),
};
this.lsp.reader.listen((data) => {
this.ws.send(JSON.stringify({ event: "lspOutput", output: data }));
});
this.ws.send(JSON.stringify({ event: "lspStarted" }));
}
}; };
cleanup = async () => { cleanup = async () => {
this.log(`Cleaning up session`); this.log(`Cleaning up session`);

View File

@ -9,6 +9,9 @@ export interface LangConfig {
alwaysCreate?: boolean; alwaysCreate?: boolean;
compile?: string; compile?: string;
run: string; run: string;
lsp?: string;
lspInit?: any;
lspConfig?: any;
template: string; template: string;
hacks?: "ghci-config"[]; hacks?: "ghci-config"[];
} }
@ -720,6 +723,14 @@ main :-
repl: "python3 -u", repl: "python3 -u",
main: "main.py", main: "main.py",
run: "python3 -u -i main.py", run: "python3 -u -i main.py",
lsp: "Microsoft.Python.LanguageServer",
lspInit: {
interpreter: {
properties: {
InterpreterPath: "/usr/bin/python3",
},
},
},
template: `print("Hello, world!") template: `print("Hello, world!")
`, `,
}, },

View File

@ -1,4 +1,17 @@
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import {
createConnection,
MonacoLanguageClient,
MonacoServices,
} from "monaco-languageclient";
import { Disposable } from "vscode";
import { createMessageConnection } from "vscode-jsonrpc";
import {
AbstractMessageReader,
DataCallback,
} from "vscode-jsonrpc/lib/messageReader";
import { AbstractMessageWriter } from "vscode-jsonrpc/lib/messageWriter";
import { Message } from "vscode-jsonrpc/lib/messages";
import { Terminal } from "xterm"; import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit"; import { FitAddon } from "xterm-addon-fit";
@ -7,9 +20,67 @@ import "xterm/css/xterm.css";
interface RijuConfig { interface RijuConfig {
id: string; id: string;
monacoLang: string; monacoLang: string;
lspInit?: any;
lspConfig?: any;
template: string; template: string;
} }
class RijuMessageReader extends AbstractMessageReader {
state: "initial" | "listening" | "closed" = "initial";
callback: DataCallback | null = null;
messageQueue: any[] = [];
socket: WebSocket;
constructor(socket: WebSocket) {
super();
this.socket = socket;
this.socket.addEventListener("message", (event: MessageEvent) => {
this.readMessage(event.data);
});
}
listen(callback: DataCallback): void {
if (this.state === "initial") {
this.state = "listening";
this.callback = callback;
while (this.messageQueue.length > 0) {
this.readMessage(this.messageQueue.pop()!);
}
}
}
readMessage(rawMessage: string): void {
if (this.state === "initial") {
this.messageQueue.splice(0, 0, rawMessage);
} else if (this.state === "listening") {
let message: any;
try {
message = JSON.parse(rawMessage);
} catch (err) {
return;
}
switch (message?.event) {
case "lspOutput":
this.callback!(message?.output);
break;
}
}
}
}
class RijuMessageWriter extends AbstractMessageWriter {
socket: WebSocket;
constructor(socket: WebSocket) {
super();
this.socket = socket;
}
write(msg: Message): void {
this.socket.send(JSON.stringify({ event: "lspInput", input: msg }));
}
}
async function main() { async function main() {
const config: RijuConfig = (window as any).rijuConfig; const config: RijuConfig = (window as any).rijuConfig;
@ -29,6 +100,7 @@ async function main() {
let retryDelayMs = initialRetryDelayMs; let retryDelayMs = initialRetryDelayMs;
function tryConnect() { function tryConnect() {
let clientDisposable: Disposable | null = null;
console.log("Connecting to server..."); console.log("Connecting to server...");
socket = new WebSocket( socket = new WebSocket(
(document.location.protocol === "http:" ? "ws://" : "wss://") + (document.location.protocol === "http:" ? "ws://" : "wss://") +
@ -60,6 +132,36 @@ async function main() {
} }
term.write(message.output); term.write(message.output);
return; return;
case "lspStarted":
const connection = createMessageConnection(
new RijuMessageReader(socket!),
new RijuMessageWriter(socket!)
);
const client = new MonacoLanguageClient({
name: "Riju",
clientOptions: {
documentSelector: [{ pattern: "**" }],
middleware: {
workspace: {
configuration: () => {
return [config.lspConfig || {}];
},
},
},
initializationOptions: config.lspInit || {},
},
connectionProvider: {
get: (errorHandler, closeHandler) =>
Promise.resolve(
createConnection(connection, errorHandler, closeHandler)
),
},
});
clientDisposable = client.start();
return;
case "lspOutput":
// Should be handled by RijuMessageReader
return;
default: default:
console.error("Unexpected message from server:", message); console.error("Unexpected message from server:", message);
return; return;
@ -71,6 +173,9 @@ async function main() {
} else { } else {
console.error("Connection died"); console.error("Connection died");
} }
if (clientDisposable) {
clientDisposable.dispose();
}
scheduleConnect(); scheduleConnect();
}); });
} }
@ -102,6 +207,8 @@ async function main() {
document.getElementById("runButton")!.addEventListener("click", () => { document.getElementById("runButton")!.addEventListener("click", () => {
socket?.send(JSON.stringify({ event: "runCode", code: editor.getValue() })); socket?.send(JSON.stringify({ event: "runCode", code: editor.getValue() }));
}); });
MonacoServices.install(editor);
} }
main().catch(console.error); main().catch(console.error);

View File

@ -4,6 +4,8 @@
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.10.3",
"@babel/preset-env": "^7.10.3",
"@types/app-root-path": "^1.2.4", "@types/app-root-path": "^1.2.4",
"@types/async-lock": "^1.1.2", "@types/async-lock": "^1.1.2",
"@types/express": "^4.17.6", "@types/express": "^4.17.6",
@ -16,6 +18,7 @@
"@types/uuid": "^8.0.0", "@types/uuid": "^8.0.0",
"app-root-path": "^3.0.0", "app-root-path": "^3.0.0",
"async-lock": "^1.2.4", "async-lock": "^1.2.4",
"babel-loader": "^8.1.0",
"css-loader": "^3.5.3", "css-loader": "^3.5.3",
"ejs": "^3.1.3", "ejs": "^3.1.3",
"express": "^4.17.1", "express": "^4.17.1",
@ -23,6 +26,8 @@
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"monaco-editor": "^0.20.0", "monaco-editor": "^0.20.0",
"monaco-editor-webpack-plugin": "^1.9.0",
"monaco-languageclient": "^0.13.0",
"node-cleanup": "^2.1.2", "node-cleanup": "^2.1.2",
"node-pty": "^0.9.0", "node-pty": "^0.9.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
@ -31,6 +36,8 @@
"ts-loader": "^7.0.5", "ts-loader": "^7.0.5",
"typescript": "^3.9.5", "typescript": "^3.9.5",
"uuid": "^8.1.0", "uuid": "^8.1.0",
"vscode": "^1.1.37",
"vscode-jsonrpc": "^5.0.1",
"webpack": "^4.43.0", "webpack": "^4.43.0",
"webpack-cli": "^3.3.11", "webpack-cli": "^3.3.11",
"xterm": "^4.6.0", "xterm": "^4.6.0",

View File

@ -60,6 +60,15 @@ tar -xf powershell-*.tar.gz -C /opt/powershell
ln -s /opt/powershell/pwsh /usr/bin/pwsh ln -s /opt/powershell/pwsh /usr/bin/pwsh
rm powershell-*.tar.gz rm powershell-*.tar.gz
# Python
cd /tmp
xml="$(curl -sSL "https://pvsc.blob.core.windows.net/python-language-server-stable?restype=container&comp=list&prefix=Python-Language-Server-linux-x64")"
nupkg="$(echo "$xml" | grep -Eo 'https://[^<]+\.nupkg' | tail -n1)"
wget -nv "${nupkg}"
unzip -d /opt/mspyls Python-Language-Server-linux-x64.*.nupkg
chmod +x /opt/mspyls/Microsoft.Python.LanguageServer
ln -s /opt/mspyls/Microsoft.Python.LanguageServer /usr/bin/Microsoft.Python.LanguageServer
# SNOBOL # SNOBOL
wget -nv ftp://ftp.snobol4.org/snobol/snobol4-2.0.tar.gz wget -nv ftp://ftp.snobol4.org/snobol/snobol4-2.0.tar.gz
tar -xf snobol4-*.tar.gz tar -xf snobol4-*.tar.gz

View File

@ -1,11 +1,13 @@
const path = require("path"); const path = require("path");
const webpack = require("webpack");
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
function isProduction(argv) { function isProduction(argv) {
return !argv.development; return !argv.development;
} }
module.exports = (_, argv) => ({ module.exports = (_, argv) => ({
devtool: isProduction(argv) ? undefined : "source-map",
entry: "./frontend/src/app.ts", entry: "./frontend/src/app.ts",
mode: isProduction(argv) ? "production" : "development", mode: isProduction(argv) ? "production" : "development",
module: { module: {
@ -26,8 +28,21 @@ module.exports = (_, argv) => ({
test: /\.ttf$/, test: /\.ttf$/,
use: ["file-loader"], use: ["file-loader"],
}, },
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
include: /vscode-jsonrpc/,
},
], ],
}, },
node: {
net: "mock",
},
output: { output: {
path: path.resolve(__dirname, "frontend/out"), path: path.resolve(__dirname, "frontend/out"),
publicPath: "/js/", publicPath: "/js/",
@ -36,4 +51,10 @@ module.exports = (_, argv) => ({
performance: { performance: {
hints: false, hints: false,
}, },
plugins: [new MonacoWebpackPlugin()],
resolve: {
alias: {
vscode: require.resolve("monaco-languageclient/lib/vscode-compatibility"),
},
},
}); });

1286
yarn.lock

File diff suppressed because it is too large Load Diff