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 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`);
|
||||||
|
|
|
@ -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!")
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue