Skip to content

Commit

Permalink
Implement language servers
Browse files Browse the repository at this point in the history
  • Loading branch information
pantherman594 committed Jul 18, 2019
1 parent 2b58770 commit 76c4127
Show file tree
Hide file tree
Showing 9 changed files with 2,187 additions and 1,985 deletions.
3,800 changes: 1,903 additions & 1,897 deletions webClient/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion webClient/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
"dependencies": {
"@angular/cdk": "~6.3.3",
"angular-tree-component": "~7.2.0",
"deepmerge": "^3.3.0",
"lodash": "~4.17.10",
"monaco-languageclient": "0.6.3",
"ngx-bootstrap": "^2.0.4",
"ngx-monaco-editor": "~6.0.0",
"ngx-perfect-scrollbar": "~5.3.5",
"reconnecting-websocket": "3.2.2",
"reconnecting-websocket": "^4.1.10",
"vscode-json-languageservice": "3.0.12",
"vscode-ws-jsonrpc": "0.0.2-2"
},
Expand Down
13 changes: 8 additions & 5 deletions webClient/src/app/core/menu-bar/menu-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,12 +442,15 @@ export class MenuBarComponent implements OnInit {

newFileRef.afterClosed().subscribe(result => {
if (result) {
this.languageServer.updateSettings(result);
if (result.enable) {
this.editorControl.connToLS.next();
} else {
this.editorControl.disFromLS.next();
for (const language in result) {
if (!result.hasOwnProperty(language)
|| result[language] !== 'disable') {
continue;
}

delete result[language];
}
this.languageServer.updateSettings(result);
}
});
}
Expand Down
120 changes: 78 additions & 42 deletions webClient/src/app/editor/code-editor/monaco/monaco.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import { MonacoService } from './monaco.service';
import { EditorControlService } from '../../../shared/editor-control/editor-control.service';
import { LanguageServerService } from '../../../shared/language-server/language-server.service';
import { Angular2InjectionTokens, Angular2PluginViewportEvents } from 'pluginlib/inject-resources';
const ReconnectingWebSocket = require('reconnecting-websocket');
import ReconnectingWebSocket from 'reconnecting-websocket';
import * as merge from 'deepmerge';

@Component({
selector: 'app-monaco',
Expand All @@ -29,6 +30,8 @@ export class MonacoComponent implements OnInit, OnChanges {
@Input() options;
@Input() editorFile;

private currentLanguage = '';

constructor(
private monacoService: MonacoService,
private editorControl: EditorControlService,
Expand All @@ -37,10 +40,10 @@ export class MonacoComponent implements OnInit, OnChanges {
@Inject(Angular2InjectionTokens.VIEWPORT_EVENTS) private viewportEvents: Angular2PluginViewportEvents) {
}

ngOnInit() {
ngOnInit(): void {
}

ngOnChanges(changes: SimpleChanges) {
ngOnChanges(changes: SimpleChanges): void {
for (const input in changes) {
if (input === 'editorFile' && changes[input].currentValue != null) {
this.monacoService.openFile(
Expand All @@ -51,26 +54,50 @@ export class MonacoComponent implements OnInit, OnChanges {
}
}

onMonacoInit(editor) {
onMonacoInit(editor): void {
this.editorControl.editor.next(editor);
this.keyBinds(editor);
this.viewportEvents.resized.subscribe(()=> {
this.viewportEvents.resized.subscribe(() => {
editor.layout()
});
/* disable for now...
this.editorControl.connToLS.subscribe((lang) => {
this.connectToLanguageServer(lang);

this.editorControl.updateLS.subscribe(() => {
const lang = this.currentLanguage;
this.onLanguageChange(undefined);
this.onLanguageChange(lang);
});
this.editorControl.fileOpened.subscribe(f => {
this.onLanguageChange(f.buffer.model.language);
});
this.editorControl.disFromLS.subscribe((lang) => {
this.closeLanguageServer(lang);
this.editorControl.changeLanguage.subscribe(f => {
this.onLanguageChange(f.language);
});
this.editorControl.closeFile.subscribe(f => {
// fileOpened handles file closes as well, as long as there is another
// open file for the editor to switch to. This listener handles when files
// are closed without another open file

if (this.editorControl.fetchActiveFile() === undefined) {
this.onLanguageChange(undefined);
}
});
}

onLanguageChange(lang: string): void {
if (this.currentLanguage === lang) {
return;
}

this.connectToLanguageServer();
*/
if (this.currentLanguage) {
this.closeLanguageServer(this.currentLanguage);
}

this.connectToLanguageServer(lang);
this.currentLanguage = lang;
}

keyBinds(editor: any) {
let self = this;
keyBinds(editor: any): void {
const self = this;
//editor.addAction({
// An unique identifier of the contributed action.
//id: 'save-all',
Expand Down Expand Up @@ -140,28 +167,17 @@ export class MonacoComponent implements OnInit, OnChanges {
});
}

connectToLanguageServer(lang?: string) {
let languages = this.languageService.getSettings().endpoint;
let connExist = this.languageService.connections.map(x => x.name);
connectToLanguageServer(lang: string): void {
const connExist = this.languageService.connections.map(x => x.name);

for (let language in languages) {
if (lang) {
if (lang === language && connExist.indexOf(language) < 0) {
this.listenTo(language);
} else {
this.log.warn(`${language} server already started!`);
}
} else {
if (connExist.indexOf(language) < 0) {
this.listenTo(language);
} else {
this.log.warn(`${language} server already started!`);
}
}
if (connExist.indexOf(lang) < 0) {
this.listenTo(lang);
} else {
this.log.warn(`${lang} server already started!`);
}
}

closeLanguageServer(lang?: string) {
closeLanguageServer(lang: string): void {
this.languageService.connections
.filter(c => {
if (lang) {
Expand All @@ -171,27 +187,38 @@ export class MonacoComponent implements OnInit, OnChanges {
}
})
.forEach(c => {
let conn = this.languageService.connections;
const conn = this.languageService.connections;
c.connection.dispose();
conn.splice(conn.indexOf(c), 1);
});
}

listenTo(lang: string) {
listenTo(lang: string): void {
const langUrl = this.createUrl(lang);

if (!langUrl) {
return;
}

const langWebSocket = this.createWebSocket(langUrl);
const langService = createMonacoServices(this.editorControl.editor.getValue());

this.log.info(`Connecting to ${lang} server`);

listen({
webSocket: langWebSocket,
// langWebSocket should be casted to WebSocket but it doesn't work
webSocket: langWebSocket as any,
onConnection: connection => {
// create and start the language client
const languageClient = this.createLanguageClient(lang, connection, langService);
const disposable = languageClient.start();
connection.onClose(() => disposable.dispose());
connection.onDispose(() => disposable.dispose());
connection.onClose(() => {
connection.dispose()
});
connection.onDispose(() => {
disposable.dispose();
langWebSocket.close();
});
this.languageService.addConnection(lang, connection);
}
});
Expand All @@ -202,7 +229,7 @@ export class MonacoComponent implements OnInit, OnChanges {
}

createLanguageClient(language: string, connection: MessageConnection, services: BaseLanguageClient.IServices): BaseLanguageClient {
return new BaseLanguageClient({
let options = {
name: `${language} language client`,
clientOptions: {
// use a language id as a document selector
Expand All @@ -212,18 +239,27 @@ export class MonacoComponent implements OnInit, OnChanges {
error: () => ErrorAction.Continue,
closed: () => CloseAction.DoNotRestart
}
},
services,
} as any,
// create a language client connection from the JSON RPC connection on demand
connectionProvider: {
get: (errorHandler, closeHandler) => {
return Promise.resolve(createConnection(connection, errorHandler, closeHandler));
}
}
});
} as any;

options = merge(
options,
this.languageService.getLanguageOptions(language),
);

// services breaks merge with "too many recursions" so it is added after
options.services = services;

return new BaseLanguageClient(options);
}

createWebSocket(wsUrl: string): WebSocket {
createWebSocket(wsUrl: string): ReconnectingWebSocket {
const socketOptions = {
maxReconnectionDelay: 10000,
minReconnectionDelay: 1000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,25 @@
Copyright Contributors to the Zowe Project.
-->
<h2 mat-dialog-title>Language Server Setting</h2>
<h2 mat-dialog-title>Language Server Settings</h2>
<mat-dialog-content>
<mat-form-field>
<textarea matInput type="text" placeholder="Put Your Config Here" [matTextareaAutosize]="true" [(ngModel)]="settings.config"></textarea>
</mat-form-field>
<section>
<label>Enable Language Server:</label>
<mat-checkbox color="primary" [(ngModel)]="settings.enable">Enable</mat-checkbox>
<section *ngFor="let language of languages">
{{language.name}}

<mat-form-field *ngIf="language.serverIds.length > 0; else elseBlock">
<mat-select placeholder="Select a plugin" [(ngModel)]="settings[language.id]">
<mat-option *ngFor="let langserver of language.serverIds" [value]="langserver">
{{language.langservers[langserver].name}}</mat-option>

<mat-option value="disable">Disable</mat-option>
</mat-select>
</mat-form-field>

<ng-template #elseBlock>
<mat-form-field>
<mat-select placeholder="No language server plugins installed" [disabled]="true"></mat-select>
</mat-form-field>
</ng-template>
</section>
</mat-dialog-content>
<mat-dialog-actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
mat-dialog-actions {
justify-content: flex-end;
}

mat-form-field {
margin-left: 5px;
}
/*
This program and the accompanying materials are
made available under the terms of the Eclipse Public License v2.0 which accompanies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,78 @@
*/
import { Component, OnInit } from '@angular/core';
import { LanguageServerService } from '../../language-server/language-server.service';
import { EditorControlService } from '../../editor-control/editor-control.service';

interface ILanguages {
id: string;
name: string;
serverIds: string[];
langservers: any;
}

interface ISettings {
[langId: string]: string;
}

@Component({
selector: 'app-language-server',
templateUrl: './language-server.component.html',
styleUrls: ['./language-server.component.scss', '../../../../styles.scss']
})

export class LanguageServerComponent implements OnInit {
private settings = {
config: '',
enable: true,
};

constructor(private languageServer: LanguageServerService) {
this.settings.config = JSON.stringify(this.languageServer.getSettings());
this.settings.enable = this.languageServer.getEnabled();
}
private settings: ISettings;

ngOnInit() {
private languages: ILanguages[];

private monaco: any;

constructor(
private languageServer: LanguageServerService,
private editorControl: EditorControlService,
) {
this.settings = this.languageServer.getSettings();

this.editorControl.editorCore.subscribe((monaco) => {
if (monaco != null) {
this.monaco = monaco;
// This is triggered after monaco initializes & is loaded with configuration items
this.resetLanguageList();
}
});
}

ngOnInit(): void { }

private resetLanguageList(): void {
const langservers = this.languageServer.getLanguageServers();

this.languages = this.monaco.languages.getLanguages()
.map((language) => ({
id: language.id,
name: language.aliases[0],
// for some reason Object.keys wasn't working from the html template
serverIds: Object.keys(langservers[language.id] || {}),
langservers: langservers[language.id],
}))
.sort((lang1, lang2) => {
if ((lang1.serverIds.length > 0) === (lang2.serverIds.length > 0)) {
const name1 = lang1.name.toLowerCase();
const name2 = lang2.name.toLowerCase();
if (name1 < name2) {
return -1;
} else if (name1 > name2) {
return 1;
} else {
return 0;
}
}
if (lang1.serverIds.length > 0) {
return -1;
}
return 1;
});
}
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ export class EditorControlService implements ZLUX.IEditor, ZLUX.IEditorMultiBuff
public initializedFile: EventEmitter<ProjectContext> = new EventEmitter();
//public saveAllFile: EventEmitter<any> = new EventEmitter();
public changeLanguage: EventEmitter<{ context: ProjectContext, language: string }> = new EventEmitter();
public connToLS: EventEmitter<string> = new EventEmitter();
public disFromLS: EventEmitter<string> = new EventEmitter();
public updateLS: EventEmitter<string> = new EventEmitter();

private _rootContext: BehaviorSubject<ProjectContext> = new BehaviorSubject<ProjectContext>(undefined);
private _context: BehaviorSubject<ProjectContext[]> = new BehaviorSubject<ProjectContext[]>(undefined);
Expand Down
Loading

0 comments on commit 76c4127

Please sign in to comment.