├── .gitignore ├── src ├── monaco-editor-core.d.ts ├── main.ts ├── index.html ├── pses-launcher.ts ├── server.ts └── client.ts ├── README.md ├── tsconfig.json ├── LICENSE ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib -------------------------------------------------------------------------------- /src/monaco-editor-core.d.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io). All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | /// -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io). All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | require('monaco-editor-core'); 6 | (self as any).MonacoEnvironment = { 7 | getWorkerUrl: () => './editor.worker.bundle.js' 8 | } 9 | require('./client'); 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monaco PowerShell Editor 2 | 3 | VSCode's Monaco Editor + [PowerShell Editor Services](https://github.com/powershell/PowerShellEditorServices)! 4 | 5 | ![image](https://user-images.githubusercontent.com/2644648/43245349-da6d8f50-9062-11e8-94c0-db1fb0ff3b26.png) 6 | 7 | Getting started: 8 | 9 | 1. Clone or download the repo 10 | 2. `./build.ps1` 11 | 3. `npm start` 12 | 13 | Shoutouts: 14 | 15 | * [VSCode's Monaco editor](https://github.com/Microsoft/monaco-editor) 16 | * [Monaco Language Client](https://github.com/TypeFox/monaco-languageclient) 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "inlineSources": false, 8 | "declaration": true, 9 | "stripInternal": true, 10 | "lib": [ 11 | "es2016", "dom" 12 | ], 13 | "rootDir":"src", 14 | "outDir": "lib", 15 | "baseUrl": ".", 16 | "strict": true, 17 | "noImplicitReturns": true, 18 | "noUnusedLocals": true 19 | }, 20 | "include": [ 21 | "src" 22 | ] 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tyler James Leonhardt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 38 | 39 | 40 | 41 |
42 |
Monaco PowerShell Editor
43 |
44 |
45 | 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monaco-powershell", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "express": "^4.15.2", 6 | "monaco-languageclient": "^0.6.3", 7 | "normalize-url": "^2.0.1", 8 | "reconnecting-websocket": "^3.2.2", 9 | "request-light": "^0.2.2", 10 | "vscode-json-languageservice": "^3.0.12", 11 | "vscode-languageserver": "^4.0.0", 12 | "vscode-uri": "^1.0.3", 13 | "vscode-ws-jsonrpc": "^0.0.2-1", 14 | "ws": "^5.0.0" 15 | }, 16 | "devDependencies": { 17 | "@types/express": "^4.0.35", 18 | "@types/node": "^7.0.12", 19 | "@types/normalize-url": "^1.9.1", 20 | "@types/ws": "0.0.39", 21 | "css-loader": "^0.28.11", 22 | "rimraf": "^2.6.2", 23 | "source-map-loader": "^0.2.3", 24 | "style-loader": "^0.20.3", 25 | "typescript": "^2.7.2", 26 | "uglifyjs-webpack-plugin": "^1.2.4", 27 | "webpack": "^3.11.0", 28 | "webpack-merge": "^4.1.2" 29 | }, 30 | "scripts": { 31 | "prepare": "npm run clean && npm run build", 32 | "compile": "tsc", 33 | "watch": "tsc -w", 34 | "clean": "rimraf lib", 35 | "copy": "cp src/index.html lib/index.html", 36 | "build": "npm run compile && webpack && npm run copy", 37 | "start": "node lib/server.js" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/pses-launcher.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io). All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import * as rpc from "vscode-ws-jsonrpc/lib"; 6 | import * as server from "vscode-ws-jsonrpc/lib/server"; 7 | import * as lsp from "vscode-languageserver/lib/main"; 8 | 9 | export function launch(socket: rpc.IWebSocket) { 10 | const reader = new rpc.WebSocketMessageReader(socket); 11 | const writer = new rpc.WebSocketMessageWriter(socket); 12 | // start the language server as an external process 13 | const socketConnection = server.createConnection(reader, writer, () => socket.dispose()); 14 | const serverConnection = server.createServerProcess('powershell', 'pwsh-preview', [__dirname + '/PowerShellEditorServices/PowerShellEditorServices/Start-EditorServices.ps1', '-HostName', 'monaco', '-HostProfileId', '0', '-HostVersion', '1.0.0', '-LogPath', __dirname + '/logs/pses.log.txt', '-LogLevel', 'Diagnostic', '-BundledModulesPath', __dirname + '/PowerShellEditorServices', '-Stdio', '-SessionDetailsPath', __dirname + '/.pses_session', '-FeatureFlags', '@()']); 15 | server.forward(socketConnection, serverConnection, message => { 16 | if (rpc.isRequestMessage(message)) { 17 | if (message.method === lsp.InitializeRequest.type.method) { 18 | const initializeParams = message.params as lsp.InitializeParams; 19 | initializeParams.processId = process.pid; 20 | } 21 | } 22 | return message; 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io). All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | const path = require('path'); 6 | const lib = path.resolve(__dirname, "lib"); 7 | 8 | const webpack = require('webpack'); 9 | const merge = require('webpack-merge'); 10 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 11 | 12 | const common = { 13 | entry: { 14 | "main": path.resolve(lib, "main.js"), 15 | "editor.worker": 'monaco-editor-core/esm/vs/editor/editor.worker.js' 16 | }, 17 | output: { 18 | filename: '[name].bundle.js', 19 | path: lib 20 | }, 21 | module: { 22 | rules: [{ 23 | test: /\.css$/, 24 | use: ['style-loader', 'css-loader'] 25 | }] 26 | }, 27 | target: 'web', 28 | node: { 29 | fs: 'empty', 30 | child_process: 'empty', 31 | net: 'empty', 32 | crypto: 'empty' 33 | } 34 | }; 35 | 36 | if (process.env['NODE_ENV'] === 'production') { 37 | module.exports = merge(common, { 38 | plugins: [ 39 | new UglifyJSPlugin(), 40 | new webpack.DefinePlugin({ 41 | 'process.env.NODE_ENV': JSON.stringify('production') 42 | }) 43 | ] 44 | }); 45 | } else { 46 | module.exports = merge(common, { 47 | devtool: 'source-map', 48 | module: { 49 | rules: [{ 50 | test: /\.js$/, 51 | enforce: 'pre', 52 | loader: 'source-map-loader' 53 | }] 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io). All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import * as ws from "ws"; 6 | import * as http from "http"; 7 | import * as url from "url"; 8 | import * as net from "net"; 9 | import * as express from "express"; 10 | import * as rpc from "vscode-ws-jsonrpc/lib"; 11 | import { launch } from "./pses-launcher"; 12 | 13 | process.on('uncaughtException', function (err: any) { 14 | console.error('Uncaught Exception: ', err.toString()); 15 | if (err.stack) { 16 | console.error(err.stack); 17 | } 18 | }); 19 | 20 | // create the express application 21 | const app = express(); 22 | // server the static content, i.e. index.html 23 | app.use(express.static(__dirname)); 24 | // start the server 25 | const server = app.listen(3000); 26 | // create the web socket 27 | const wss = new ws.Server({ 28 | noServer: true, 29 | perMessageDeflate: false 30 | }); 31 | server.on('upgrade', (request: http.IncomingMessage, socket: net.Socket, head: Buffer) => { 32 | const pathname = request.url ? url.parse(request.url).pathname : undefined; 33 | if (pathname === '/sampleServer') { 34 | wss.handleUpgrade(request, socket, head, webSocket => { 35 | const socket: rpc.IWebSocket = { 36 | send: content => webSocket.send(content, error => { 37 | if (error) { 38 | throw error; 39 | } 40 | }), 41 | onMessage: cb => webSocket.on('message', cb), 42 | onError: cb => webSocket.on('error', cb), 43 | onClose: cb => webSocket.on('close', cb), 44 | dispose: () => webSocket.close() 45 | }; 46 | // launch the server when the web socket is opened 47 | if (webSocket.readyState === webSocket.OPEN) { 48 | launch(socket); 49 | } else { 50 | webSocket.on('open', () => launch(socket)); 51 | } 52 | }); 53 | } 54 | }) -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2018 TypeFox GmbH (http://www.typefox.io). All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | import { listen, MessageConnection } from 'vscode-ws-jsonrpc/lib'; 6 | import { 7 | BaseLanguageClient, CloseAction, ErrorAction, 8 | createMonacoServices, createConnection 9 | } from 'monaco-languageclient/lib'; 10 | import normalizeUrl = require('normalize-url'); 11 | const ReconnectingWebSocket = require('reconnecting-websocket'); 12 | 13 | // register Monaco languages 14 | monaco.languages.register({ 15 | id: 'powershell', 16 | extensions: ['.ps1', '.psd1', '.psm1'], 17 | aliases: ['pwsh', 'PowerShell'], 18 | mimetypes: ['text/plain'], 19 | }); 20 | 21 | // create Monaco editor 22 | const value = `Write-Host "Hello World!"`; 23 | const editor = monaco.editor.create(document.getElementById("container")!, { 24 | model: monaco.editor.createModel(value, 'powershell', monaco.Uri.parse('inmemory://model.ps1')), 25 | glyphMargin: true, 26 | lightbulb: { 27 | enabled: true 28 | }, 29 | fixedOverflowWidgets: true, 30 | automaticLayout: true, 31 | scrollBeyondLastLine: false 32 | }); 33 | 34 | window.onresize = () => { 35 | editor.layout({ width: window.innerWidth, height: window.innerHeight }); 36 | }; 37 | 38 | // create the web socket 39 | const url = createUrl('/sampleServer') 40 | const webSocket = createWebSocket(url); 41 | // listen when the web socket is opened 42 | listen({ 43 | webSocket, 44 | onConnection: connection => { 45 | // create and start the language client 46 | const languageClient = createLanguageClient(connection); 47 | const disposable = languageClient.start(); 48 | connection.onClose(() => disposable.dispose()); 49 | } 50 | }); 51 | 52 | const services = createMonacoServices(editor); 53 | function createLanguageClient(connection: MessageConnection): BaseLanguageClient { 54 | return new BaseLanguageClient({ 55 | name: "Monaco PowerShell", 56 | clientOptions: { 57 | // use a language id as a document selector 58 | documentSelector: ['powershell'], 59 | // disable the default error handler 60 | errorHandler: { 61 | error: () => ErrorAction.Continue, 62 | closed: () => CloseAction.DoNotRestart 63 | } 64 | }, 65 | services, 66 | // create a language client connection from the JSON RPC connection on demand 67 | connectionProvider: { 68 | get: (errorHandler, closeHandler) => { 69 | return Promise.resolve(createConnection(connection, errorHandler, closeHandler)) 70 | } 71 | } 72 | }) 73 | } 74 | 75 | function createUrl(path: string): string { 76 | const protocol = location.protocol === 'https:' ? 'wss' : 'ws'; 77 | return normalizeUrl(`${protocol}://${location.host}${location.pathname}${path}`); 78 | } 79 | 80 | function createWebSocket(url: string): WebSocket { 81 | const socketOptions = { 82 | maxReconnectionDelay: 10000, 83 | minReconnectionDelay: 1000, 84 | reconnectionDelayGrowFactor: 1.3, 85 | connectionTimeout: 10000, 86 | maxRetries: Infinity, 87 | debug: false 88 | }; 89 | return new ReconnectingWebSocket(url, undefined, socketOptions); 90 | } 91 | --------------------------------------------------------------------------------