LSP working with Python!!
This commit is contained in:
parent
fc9cbbc7c6
commit
4425f31b88
|
@ -1,8 +1,10 @@
|
|||
import { ChildProcess, spawn } from "child_process";
|
||||
import * as path from "path";
|
||||
import * as WebSocket from "ws";
|
||||
|
||||
import * as pty from "node-pty";
|
||||
import { IPty } from "node-pty";
|
||||
import * as rpc from "vscode-jsonrpc";
|
||||
import { v4 as getUUID } from "uuid";
|
||||
|
||||
import { PRIVILEGED } from "./config";
|
||||
|
@ -15,6 +17,11 @@ export class Session {
|
|||
code: string;
|
||||
config: LangConfig;
|
||||
term: { pty: IPty | null; live: boolean };
|
||||
lsp: {
|
||||
proc: ChildProcess;
|
||||
reader: rpc.StreamMessageReader;
|
||||
writer: rpc.StreamMessageWriter;
|
||||
} | null;
|
||||
ws: WebSocket;
|
||||
homedir: string | null;
|
||||
uid: number | null;
|
||||
|
@ -28,6 +35,7 @@ export class Session {
|
|||
this.ws = ws;
|
||||
this.config = langs[lang];
|
||||
this.term = { pty: null, live: false };
|
||||
this.lsp = null;
|
||||
this.code = "";
|
||||
this.homedir = null;
|
||||
this.uid = null;
|
||||
|
@ -83,6 +91,13 @@ export class Session {
|
|||
this.run();
|
||||
}
|
||||
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:
|
||||
this.log(`Got unknown message type: ${msg.event}`);
|
||||
break;
|
||||
|
@ -103,6 +118,7 @@ export class Session {
|
|||
alwaysCreate,
|
||||
compile,
|
||||
run,
|
||||
lsp,
|
||||
hacks,
|
||||
} = this.config;
|
||||
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 () => {
|
||||
this.log(`Cleaning up session`);
|
||||
|
|
|
@ -9,6 +9,9 @@ export interface LangConfig {
|
|||
alwaysCreate?: boolean;
|
||||
compile?: string;
|
||||
run: string;
|
||||
lsp?: string;
|
||||
lspInit?: any;
|
||||
lspConfig?: any;
|
||||
template: string;
|
||||
hacks?: "ghci-config"[];
|
||||
}
|
||||
|
@ -720,6 +723,14 @@ main :-
|
|||
repl: "python3 -u",
|
||||
main: "main.py",
|
||||
run: "python3 -u -i main.py",
|
||||
lsp: "Microsoft.Python.LanguageServer",
|
||||
lspInit: {
|
||||
interpreter: {
|
||||
properties: {
|
||||
InterpreterPath: "/usr/bin/python3",
|
||||
},
|
||||
},
|
||||
},
|
||||
template: `print("Hello, world!")
|
||||
`,
|
||||
},
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
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 { FitAddon } from "xterm-addon-fit";
|
||||
|
||||
|
@ -7,9 +20,67 @@ import "xterm/css/xterm.css";
|
|||
interface RijuConfig {
|
||||
id: string;
|
||||
monacoLang: string;
|
||||
lspInit?: any;
|
||||
lspConfig?: any;
|
||||
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() {
|
||||
const config: RijuConfig = (window as any).rijuConfig;
|
||||
|
||||
|
@ -29,6 +100,7 @@ async function main() {
|
|||
let retryDelayMs = initialRetryDelayMs;
|
||||
|
||||
function tryConnect() {
|
||||
let clientDisposable: Disposable | null = null;
|
||||
console.log("Connecting to server...");
|
||||
socket = new WebSocket(
|
||||
(document.location.protocol === "http:" ? "ws://" : "wss://") +
|
||||
|
@ -60,6 +132,36 @@ async function main() {
|
|||
}
|
||||
term.write(message.output);
|
||||
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:
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
|
@ -71,6 +173,9 @@ async function main() {
|
|||
} else {
|
||||
console.error("Connection died");
|
||||
}
|
||||
if (clientDisposable) {
|
||||
clientDisposable.dispose();
|
||||
}
|
||||
scheduleConnect();
|
||||
});
|
||||
}
|
||||
|
@ -102,6 +207,8 @@ async function main() {
|
|||
document.getElementById("runButton")!.addEventListener("click", () => {
|
||||
socket?.send(JSON.stringify({ event: "runCode", code: editor.getValue() }));
|
||||
});
|
||||
|
||||
MonacoServices.install(editor);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.10.3",
|
||||
"@babel/preset-env": "^7.10.3",
|
||||
"@types/app-root-path": "^1.2.4",
|
||||
"@types/async-lock": "^1.1.2",
|
||||
"@types/express": "^4.17.6",
|
||||
|
@ -16,6 +18,7 @@
|
|||
"@types/uuid": "^8.0.0",
|
||||
"app-root-path": "^3.0.0",
|
||||
"async-lock": "^1.2.4",
|
||||
"babel-loader": "^8.1.0",
|
||||
"css-loader": "^3.5.3",
|
||||
"ejs": "^3.1.3",
|
||||
"express": "^4.17.1",
|
||||
|
@ -23,6 +26,8 @@
|
|||
"file-loader": "^6.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||
"monaco-languageclient": "^0.13.0",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"node-pty": "^0.9.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
@ -31,6 +36,8 @@
|
|||
"ts-loader": "^7.0.5",
|
||||
"typescript": "^3.9.5",
|
||||
"uuid": "^8.1.0",
|
||||
"vscode": "^1.1.37",
|
||||
"vscode-jsonrpc": "^5.0.1",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"xterm": "^4.6.0",
|
||||
|
|
|
@ -60,6 +60,15 @@ tar -xf powershell-*.tar.gz -C /opt/powershell
|
|||
ln -s /opt/powershell/pwsh /usr/bin/pwsh
|
||||
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
|
||||
wget -nv ftp://ftp.snobol4.org/snobol/snobol4-2.0.tar.gz
|
||||
tar -xf snobol4-*.tar.gz
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||
|
||||
function isProduction(argv) {
|
||||
return !argv.development;
|
||||
}
|
||||
|
||||
module.exports = (_, argv) => ({
|
||||
devtool: isProduction(argv) ? undefined : "source-map",
|
||||
entry: "./frontend/src/app.ts",
|
||||
mode: isProduction(argv) ? "production" : "development",
|
||||
module: {
|
||||
|
@ -26,8 +28,21 @@ module.exports = (_, argv) => ({
|
|||
test: /\.ttf$/,
|
||||
use: ["file-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-env"],
|
||||
},
|
||||
},
|
||||
include: /vscode-jsonrpc/,
|
||||
},
|
||||
],
|
||||
},
|
||||
node: {
|
||||
net: "mock",
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, "frontend/out"),
|
||||
publicPath: "/js/",
|
||||
|
@ -36,4 +51,10 @@ module.exports = (_, argv) => ({
|
|||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
plugins: [new MonacoWebpackPlugin()],
|
||||
resolve: {
|
||||
alias: {
|
||||
vscode: require.resolve("monaco-languageclient/lib/vscode-compatibility"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue