├── .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 | 
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 |
--------------------------------------------------------------------------------