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