Skip to content

Commit 6a35d5a

Browse files
authored
Merge pull request #80 from glennsarti/gh-61-refactor-client-ui
(GH-61) Refactor code to use a connection Manager
2 parents f1f25c4 + c522202 commit 6a35d5a

File tree

6 files changed

+341
-163
lines changed

6 files changed

+341
-163
lines changed

client/src/commands/puppetResourceCommand.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
'use strict';
22

33
import * as vscode from 'vscode';
4-
import { LanguageClient, RequestType } from 'vscode-languageclient';
54
import { PuppetResourceRequestParams, PuppetResourceRequest } from '../messages';
5+
import { IConnectionManager, ConnectionStatus } from '../connection';
66

77
class RequestParams implements PuppetResourceRequestParams {
88
typename: string;
99
title: string;
1010
}
1111

1212
export class puppetResourceCommand {
13-
private _langServer: LanguageClient = undefined;
13+
private _connectionManager: IConnectionManager = undefined;
1414

15-
constructor(
16-
private langServer: LanguageClient
17-
) {
18-
this._langServer = langServer;
15+
constructor(connMgr: IConnectionManager) {
16+
this._connectionManager = connMgr;
1917
}
2018

2119
private pickPuppetResource(): Thenable<string> {
@@ -28,6 +26,13 @@ export class puppetResourceCommand {
2826
}
2927

3028
public run() {
29+
var thisCommand = this
30+
31+
if (thisCommand._connectionManager.status != ConnectionStatus.Running ) {
32+
vscode.window.showInformationMessage("Puppet Resource is not available as the Language Server is not ready");
33+
return
34+
}
35+
3136
this.pickPuppetResource().then((moduleName) => {
3237
if (moduleName) {
3338

@@ -38,7 +43,7 @@ export class puppetResourceCommand {
3843
let requestParams = new RequestParams;
3944
requestParams.typename = moduleName;
4045

41-
this._langServer
46+
thisCommand._connectionManager.languageClient
4247
.sendRequest(PuppetResourceRequest.type, requestParams)
4348
.then( (resourceResult) => {
4449
if (resourceResult.error != undefined && resourceResult.error.length > 0) {

client/src/connection.ts

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
import net = require('net');
2+
import path = require('path');
3+
import vscode = require('vscode');
4+
import cp = require('child_process');
5+
import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient';
6+
import { setupPuppetCommands } from '../src/puppetcommands';
7+
import * as messages from '../src/messages';
8+
9+
const langID = 'puppet'; // don't change this
10+
11+
export enum ConnectionStatus {
12+
NotStarted,
13+
Starting,
14+
Running,
15+
Stopping,
16+
Failed
17+
}
18+
19+
export enum ConnectionType {
20+
Unknown,
21+
Local,
22+
Remote
23+
}
24+
25+
export interface IConnectionConfiguration {
26+
type: ConnectionType;
27+
host: string;
28+
port: number;
29+
stopOnClientExit: string;
30+
timeout: string;
31+
preLoadPuppet: string;
32+
}
33+
34+
export interface IConnectionManager {
35+
status: ConnectionStatus;
36+
languageClient: LanguageClient;
37+
}
38+
39+
export class ConnectionManager implements IConnectionManager {
40+
private connectionStatus: ConnectionStatus;
41+
private statusBarItem: vscode.StatusBarItem;
42+
private connectionConfiguration: IConnectionConfiguration;
43+
private languageServerClient: LanguageClient = undefined;
44+
private languageServerProcess = undefined;
45+
private puppetOutputChannel = undefined;
46+
private extensionContext = undefined;
47+
private commandsRegistered = false;
48+
49+
public get status() : ConnectionStatus {
50+
return this.connectionStatus;
51+
}
52+
public get languageClient() : LanguageClient {
53+
return this.languageServerClient;
54+
}
55+
56+
constructor(context: vscode.ExtensionContext) {
57+
this.puppetOutputChannel = vscode.window.createOutputChannel('Puppet');
58+
59+
this.extensionContext = context;
60+
}
61+
62+
public start(connectionConfig: IConnectionConfiguration) {
63+
// Setup the configuration
64+
this.connectionConfiguration = connectionConfig;
65+
this.connectionConfiguration.type = ConnectionType.Unknown;
66+
var contextPath = this.extensionContext.asAbsolutePath(path.join('vendor', 'languageserver', 'puppet-languageserver'));
67+
68+
if (!this.commandsRegistered) {
69+
this.puppetOutputChannel.appendLine('Configuring commands');
70+
console.log('Configuring commands');
71+
72+
setupPuppetCommands(langID, this, this.extensionContext);
73+
this.commandsRegistered = true;
74+
}
75+
76+
this.createStatusBarItem();
77+
78+
if (this.connectionConfiguration.host == '127.0.0.1' ||
79+
this.connectionConfiguration.host == 'localhost' ||
80+
this.connectionConfiguration.host == '') {
81+
this.connectionConfiguration.type = ConnectionType.Local
82+
} else {
83+
this.connectionConfiguration.type = ConnectionType.Remote
84+
}
85+
86+
this.languageServerClient = undefined
87+
this.languageServerProcess = undefined
88+
this.setConnectionStatus("Starting Puppet...", ConnectionStatus.Starting);
89+
90+
if (this.connectionConfiguration.type == ConnectionType.Local) {
91+
this.languageServerProcess = this.createLanguageServerProcess(contextPath, this.puppetOutputChannel);
92+
if (this.languageServerProcess == undefined) { throw new Error('Unable to start the server process'); }
93+
94+
this.languageServerProcess.stdout.on('data', (data) => {
95+
console.log("OUTPUT: " + data.toString());
96+
this.puppetOutputChannel.appendLine("OUTPUT: " + data.toString());
97+
98+
// If the language client isn't already running and it's sent the trigger text, start up a client
99+
if ( (this.languageServerClient == undefined) && (data.toString().match("LANGUAGE SERVER RUNNING") != null) ) {
100+
this.languageServerClient = this.startLangClientTCP();
101+
this.extensionContext.subscriptions.push(this.languageServerClient.start());
102+
}
103+
});
104+
105+
this.languageServerProcess.on('close', (exitCode) => {
106+
console.log("SERVER terminated with exit code: " + exitCode);
107+
this.puppetOutputChannel.appendLine("SERVER terminated with exit code: " + exitCode);
108+
});
109+
}
110+
else {
111+
this.languageServerClient = this.startLangClientTCP();
112+
this.extensionContext.subscriptions.push(this.languageServerClient.start());
113+
}
114+
115+
console.log('Congratulations, your extension "vscode-puppet" is now active!');
116+
this.puppetOutputChannel.appendLine('Congratulations, your extension "vscode-puppet" is now active!');
117+
}
118+
119+
public stop() {
120+
console.log('Stopping...');
121+
this.puppetOutputChannel.appendLine('Stopping...')
122+
123+
if (this.connectionStatus === ConnectionStatus.Failed) {
124+
this.languageServerClient = undefined;
125+
this.languageServerProcess = undefined;
126+
}
127+
128+
this.connectionStatus = ConnectionStatus.Stopping;
129+
130+
// Close the language server client
131+
if (this.languageServerClient !== undefined) {
132+
this.languageServerClient.stop();
133+
this.languageServerClient = undefined;
134+
}
135+
136+
// Kill the language server process we spawned
137+
if (this.languageServerProcess !== undefined) {
138+
// Terminate Language Server process
139+
// TODO: May not be functional on Windows.
140+
// Perhaps send the exit command and wait for process to exit?
141+
this.languageServerProcess.kill();
142+
this.languageServerProcess = undefined;
143+
}
144+
145+
this.connectionStatus = ConnectionStatus.NotStarted;
146+
147+
console.log('Stopped');
148+
this.puppetOutputChannel.appendLine('Stopped');
149+
}
150+
151+
public dispose() : void {
152+
console.log('Disposing...');
153+
this.puppetOutputChannel.appendLine('Disposing...');
154+
// Stop the current session
155+
this.stop();
156+
157+
// Dispose of any subscriptions
158+
this.extensionContext.subscriptions.forEach(item => { item.dispose(); });
159+
this.extensionContext.subscriptions.clear();
160+
}
161+
162+
private createLanguageServerProcess(serverExe: string, myOutputChannel: vscode.OutputChannel) {
163+
myOutputChannel.appendLine('Language server found at: ' + serverExe)
164+
165+
let cmd = '';
166+
let args = [serverExe];
167+
let options = {};
168+
169+
// type Platform = 'aix'
170+
// | 'android'
171+
// | 'darwin'
172+
// | 'freebsd'
173+
// | 'linux'
174+
// | 'openbsd'
175+
// | 'sunos'
176+
// | 'win32';
177+
switch (process.platform) {
178+
case 'win32':
179+
myOutputChannel.appendLine('Windows spawn process does not work at the moment')
180+
vscode.window.showErrorMessage('Windows spawn process does not work at the moment. Functionality will be limited to syntax highlighting');
181+
break;
182+
default:
183+
myOutputChannel.appendLine('Starting language server')
184+
cmd = 'ruby'
185+
options = {
186+
shell: true,
187+
env: process.env,
188+
stdio: 'pipe',
189+
};
190+
}
191+
192+
console.log("Starting the language server with " + cmd + " " + args.join(" "));
193+
var proc = cp.spawn(cmd, args, options)
194+
console.log("ProcID = " + proc.pid);
195+
myOutputChannel.appendLine('Language server PID:' + proc.pid)
196+
197+
return proc;
198+
}
199+
200+
private startLangClientTCP(): LanguageClient {
201+
this.puppetOutputChannel.appendLine('Configuring language server options')
202+
203+
var connMgr:ConnectionManager = this;
204+
let serverOptions: ServerOptions = function () {
205+
return new Promise((resolve, reject) => {
206+
var client = new net.Socket();
207+
client.connect(connMgr.connectionConfiguration.port, connMgr.connectionConfiguration.host, function () {
208+
resolve({ reader: client, writer: client });
209+
});
210+
client.on('error', function (err) {
211+
console.log(`[Puppet Lang Server Client] ` + err);
212+
connMgr.setSessionFailure("Could not start language client: ", err.message);
213+
214+
return null;
215+
})
216+
});
217+
}
218+
219+
this.puppetOutputChannel.appendLine('Configuring language server client options')
220+
let clientOptions: LanguageClientOptions = {
221+
documentSelector: [langID],
222+
}
223+
224+
this.puppetOutputChannel.appendLine(`Starting language server client (host ${this.connectionConfiguration.host} port ${this.connectionConfiguration.port})`)
225+
var title = `tcp lang server (host ${this.connectionConfiguration.host} port ${this.connectionConfiguration.port})`;
226+
var languageServerClient = new LanguageClient(title, serverOptions, clientOptions)
227+
languageServerClient.onReady().then(() => {
228+
this.puppetOutputChannel.appendLine('Language server client started, setting puppet version')
229+
languageServerClient.sendRequest(messages.PuppetVersionRequest.type).then((versionDetails) => {
230+
this.setConnectionStatus(versionDetails.puppetVersion, ConnectionStatus.Running);
231+
});
232+
}, (reason) => {
233+
this.setSessionFailure("Could not start language service: ", reason);
234+
});
235+
236+
return languageServerClient;
237+
}
238+
239+
private restartConnection(connectionConfig?: IConnectionConfiguration) {
240+
this.stop();
241+
this.start(connectionConfig);
242+
}
243+
244+
private createStatusBarItem() {
245+
if (this.statusBarItem === undefined) {
246+
this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 1);
247+
248+
// TODO: Add a command here to show the connection menu
249+
// this.statusBarItem.command = this.ShowConnectionMenuCommandName;
250+
this.statusBarItem.show();
251+
vscode.window.onDidChangeActiveTextEditor(textEditor => {
252+
if (textEditor === undefined || textEditor.document.languageId !== "puppet") {
253+
this.statusBarItem.hide();
254+
}
255+
else {
256+
this.statusBarItem.show();
257+
}
258+
})
259+
}
260+
}
261+
262+
private setConnectionStatus(statusText: string, status: ConnectionStatus): void {
263+
// Set color and icon for 'Running' by default
264+
var statusIconText = "$(terminal) ";
265+
var statusColor = "#affc74";
266+
267+
if (status == ConnectionStatus.Starting) {
268+
statusIconText = "$(sync) ";
269+
statusColor = "#f3fc74";
270+
}
271+
else if (status == ConnectionStatus.Failed) {
272+
statusIconText = "$(alert) ";
273+
statusColor = "#fcc174";
274+
}
275+
276+
this.connectionStatus = status;
277+
this.statusBarItem.color = statusColor;
278+
this.statusBarItem.text = statusIconText + statusText;
279+
}
280+
281+
private setSessionFailure(message: string, ...additionalMessages: string[]) {
282+
this.setConnectionStatus("Starting Error", ConnectionStatus.Failed);
283+
}
284+
}

0 commit comments

Comments
 (0)