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
	
	 Radon Rosborough
						Radon Rosborough